Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52c399d4d1 |
@@ -1,96 +0,0 @@
|
|||||||
parameters:
|
|
||||||
- name: Packages
|
|
||||||
type: object
|
|
||||||
default: {}
|
|
||||||
- name: LinuxImage
|
|
||||||
type: string
|
|
||||||
default: "ubuntu-latest"
|
|
||||||
- name: DotNetSdkVersion
|
|
||||||
type: string
|
|
||||||
default: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: CompatibilityCheck
|
|
||||||
displayName: Compatibility Check
|
|
||||||
pool:
|
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
|
||||||
# only execute for pull requests
|
|
||||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
${{ each Package in parameters.Packages }}:
|
|
||||||
${{ Package.key }}:
|
|
||||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
|
||||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
|
||||||
maxParallel: 2
|
|
||||||
dependsOn: MainBuild
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: "Update DotNet"
|
|
||||||
inputs:
|
|
||||||
packageType: sdk
|
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
|
||||||
|
|
||||||
- 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"
|
|
||||||
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"
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
|
||||||
contents: "**/*.dll"
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: true
|
|
||||||
|
|
||||||
- task: DownloadGitHubRelease@0
|
|
||||||
displayName: "Download ABI Compatibility Check Tool"
|
|
||||||
inputs:
|
|
||||||
connection: Jellyfin Release Download
|
|
||||||
userRepository: EraYaN/dotnet-compatibility
|
|
||||||
defaultVersionType: "latest"
|
|
||||||
itemPattern: "**-ci.zip"
|
|
||||||
downloadPath: "$(System.ArtifactsDirectory)"
|
|
||||||
|
|
||||||
- task: ExtractFiles@1
|
|
||||||
displayName: "Extract ABI Compatibility Check Tool"
|
|
||||||
inputs:
|
|
||||||
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
|
|
||||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
|
||||||
cleanDestinationFolder: true
|
|
||||||
|
|
||||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Execute ABI Compatibility Check Tool"
|
|
||||||
inputs:
|
|
||||||
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
|
|
||||||
workingDirectory: $(System.ArtifactsDirectory)
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
parameters:
|
|
||||||
LinuxImage: "ubuntu-latest"
|
|
||||||
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
|
|
||||||
DotNetSdkVersion: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: MainBuild
|
|
||||||
displayName: Main Build
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Release:
|
|
||||||
BuildConfiguration: Release
|
|
||||||
Debug:
|
|
||||||
BuildConfiguration: Debug
|
|
||||||
maxParallel: 2
|
|
||||||
pool:
|
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
|
||||||
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: "Clone Web Client (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"
|
|
||||||
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 Client"
|
|
||||||
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 Web Client"
|
|
||||||
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
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: 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@0
|
|
||||||
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@0
|
|
||||||
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@0
|
|
||||||
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@0
|
|
||||||
displayName: "Publish Artifact Common"
|
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
|
||||||
inputs:
|
|
||||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
|
||||||
artifactName: "Jellyfin.Common"
|
|
||||||
@@ -1,65 +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: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: MainTest
|
|
||||||
displayName: Main Test
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
${{ each imageName in parameters.ImageNames }}:
|
|
||||||
${{ imageName.key }}:
|
|
||||||
ImageName: ${{ imageName.value }}
|
|
||||||
maxParallel: 3
|
|
||||||
pool:
|
|
||||||
vmImage: "$(ImageName)"
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: false
|
|
||||||
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: "Update DotNet"
|
|
||||||
inputs:
|
|
||||||
packageType: sdk
|
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
|
||||||
displayName: Run .NET Core CLI tests
|
|
||||||
inputs:
|
|
||||||
command: "test"
|
|
||||||
projects: ${{ parameters.TestProjects }}
|
|
||||||
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
|
|
||||||
publishTestResults: true
|
|
||||||
testRunTitle: $(Agent.JobName)
|
|
||||||
workingDirectory: "$(Build.SourcesDirectory)"
|
|
||||||
|
|
||||||
- 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: ReportGenerator (merge)
|
|
||||||
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
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: "tests/**/*Tests.csproj"
|
|
||||||
DotNetSdkVersion: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: PublishWindows
|
|
||||||
displayName: Publish Windows
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ parameters.WindowsImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), 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: "Clone Web Client (PR)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), 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"
|
|
||||||
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')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
versionSpec: "10.x"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Build Web Client"
|
|
||||||
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')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy Web Client"
|
|
||||||
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')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: false
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone UX Repository"
|
|
||||||
inputs:
|
|
||||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: "Build NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
targetType: "filePath"
|
|
||||||
filePath: ./deployment/windows/build-jellyfin.ps1
|
|
||||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
|
||||||
errorActionPreference: "stop"
|
|
||||||
workingDirectory: $(Build.SourcesDirectory)
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
|
|
||||||
contents: "jellyfin*.exe"
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: true
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: "Publish Artifact Setup"
|
|
||||||
condition: succeeded()
|
|
||||||
inputs:
|
|
||||||
targetPath: "$(build.artifactstagingdirectory)/setup"
|
|
||||||
artifactName: "Jellyfin Server Setup"
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
|
||||||
|
|
||||||
variables:
|
|
||||||
- name: TestProjects
|
|
||||||
value: "tests/**/*Tests.csproj"
|
|
||||||
- name: RestoreBuildProjects
|
|
||||||
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
|
||||||
- name: DotNetSdkVersion
|
|
||||||
value: 3.1.100
|
|
||||||
|
|
||||||
pr:
|
|
||||||
autoCancel: true
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
batch: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- template: azure-pipelines-main.yml
|
|
||||||
parameters:
|
|
||||||
LinuxImage: "ubuntu-latest"
|
|
||||||
RestoreBuildProjects: $(RestoreBuildProjects)
|
|
||||||
|
|
||||||
- template: azure-pipelines-test.yml
|
|
||||||
parameters:
|
|
||||||
ImageNames:
|
|
||||||
Linux: "ubuntu-latest"
|
|
||||||
Windows: "windows-latest"
|
|
||||||
macOS: "macos-latest"
|
|
||||||
|
|
||||||
- template: azure-pipelines-windows.yml
|
|
||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: $(TestProjects)
|
|
||||||
|
|
||||||
- template: azure-pipelines-compat.yml
|
|
||||||
parameters:
|
|
||||||
Packages:
|
|
||||||
Naming:
|
|
||||||
NugetPackageName: Jellyfin.Naming
|
|
||||||
AssemblyFileName: Emby.Naming.dll
|
|
||||||
Controller:
|
|
||||||
NugetPackageName: Jellyfin.Controller
|
|
||||||
AssemblyFileName: MediaBrowser.Controller.dll
|
|
||||||
Model:
|
|
||||||
NugetPackageName: Jellyfin.Model
|
|
||||||
AssemblyFileName: MediaBrowser.Model.dll
|
|
||||||
Common:
|
|
||||||
NugetPackageName: Jellyfin.Common
|
|
||||||
AssemblyFileName: MediaBrowser.Common.dll
|
|
||||||
LinuxImage: "ubuntu-latest"
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
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)"
|
|
||||||
@@ -8,4 +8,3 @@ README.md
|
|||||||
deployment/*/dist
|
deployment/*/dist
|
||||||
deployment/*/pkg-dist
|
deployment/*/pkg-dist
|
||||||
deployment/collect-dist/
|
deployment/collect-dist/
|
||||||
ci/
|
|
||||||
|
|||||||
+2
-20
@@ -1,30 +1,12 @@
|
|||||||
---
|
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
name: build-debug
|
name: build
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: submodules
|
- name: submodules
|
||||||
image: docker:git
|
image: docker:git
|
||||||
commands:
|
commands:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
image: microsoft/dotnet:2-sdk
|
image: microsoft/dotnet:2-sdk
|
||||||
commands:
|
commands:
|
||||||
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
|
- dotnet publish --configuration release --output /release Jellyfin.Server
|
||||||
|
|
||||||
---
|
|
||||||
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"
|
|
||||||
|
|
||||||
|
|||||||
+9
-75
@@ -15,10 +15,6 @@ insert_final_newline = true
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
max_line_length = null
|
max_line_length = null
|
||||||
|
|
||||||
# YAML indentation
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# XML indentation
|
# XML indentation
|
||||||
[*.{csproj,xml}]
|
[*.{csproj,xml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
@@ -59,77 +55,15 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent
|
|||||||
###############################
|
###############################
|
||||||
# Naming Conventions #
|
# Naming Conventions #
|
||||||
###############################
|
###############################
|
||||||
# Style Definitions (From Roslyn)
|
# Style Definitions
|
||||||
|
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||||
# Non-private static fields are PascalCase
|
# Use PascalCase for constant fields
|
||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||||
|
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
|
||||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
|
||||||
|
|
||||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
|
||||||
|
|
||||||
# Constants are PascalCase
|
|
||||||
dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants
|
|
||||||
dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.constants.applicable_kinds = field, local
|
|
||||||
dotnet_naming_symbols.constants.required_modifiers = const
|
|
||||||
|
|
||||||
dotnet_naming_style.constant_style.capitalization = pascal_case
|
|
||||||
|
|
||||||
# Static fields are camelCase and start with s_
|
|
||||||
dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields
|
|
||||||
dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.static_fields.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.static_fields.required_modifiers = static
|
|
||||||
|
|
||||||
dotnet_naming_style.static_field_style.capitalization = camel_case
|
|
||||||
dotnet_naming_style.static_field_style.required_prefix = _
|
|
||||||
|
|
||||||
# Instance fields are camelCase and start with _
|
|
||||||
dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields
|
|
||||||
dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.instance_fields.applicable_kinds = field
|
|
||||||
|
|
||||||
dotnet_naming_style.instance_field_style.capitalization = camel_case
|
|
||||||
dotnet_naming_style.instance_field_style.required_prefix = _
|
|
||||||
|
|
||||||
# Locals and parameters are camelCase
|
|
||||||
dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters
|
|
||||||
dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local
|
|
||||||
|
|
||||||
dotnet_naming_style.camel_case_style.capitalization = camel_case
|
|
||||||
|
|
||||||
# Local functions are PascalCase
|
|
||||||
dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions
|
|
||||||
dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.local_functions.applicable_kinds = local_function
|
|
||||||
|
|
||||||
dotnet_naming_style.local_function_style.capitalization = pascal_case
|
|
||||||
|
|
||||||
# By default, name items with PascalCase
|
|
||||||
dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion
|
|
||||||
dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members
|
|
||||||
dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style
|
|
||||||
|
|
||||||
dotnet_naming_symbols.all_members.applicable_kinds = *
|
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# C# Coding Conventions #
|
# C# Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
|||||||
@@ -1,5 +1 @@
|
|||||||
* text=auto eol=lf
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
|
|
||||||
CONTRIBUTORS.md merge=union
|
CONTRIBUTORS.md merge=union
|
||||||
|
|||||||
@@ -8,36 +8,28 @@ assignees: ''
|
|||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
<!-- A clear and concise description of what the bug is. -->
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
**System (please complete the following information):**
|
|
||||||
- OS: [e.g. Debian, Windows]
|
|
||||||
- Virtualization: [e.g. Docker, KVM, LXC]
|
|
||||||
- Clients: [Browser, Android, Fire Stick, etc.]
|
|
||||||
- Browser: [e.g. Firefox 72, Chrome 80, Safari 13]
|
|
||||||
- Jellyfin Version: [e.g. 10.4.3, nightly 20191231]
|
|
||||||
- Playback: [Direct Play, Remux, Direct Stream, Transcode]
|
|
||||||
- Installed Plugins: [e.g. none, Fanart, Anime, etc.]
|
|
||||||
- Reverse Proxy: [e.g. none, nginx, apache, etc.]
|
|
||||||
- Base URL: [e.g. none, yes: /example]
|
|
||||||
- Networking: [e.g. Host, Bridge/NAT]
|
|
||||||
- Storage: [e.g. local, NFS, cloud]
|
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
<!-- Steps to reproduce the behavior: -->
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
4. See error
|
4. See error
|
||||||
|
|
||||||
**Expected behavior**
|
**Expected behavior**
|
||||||
<!-- A clear and concise description of what you expected to happen. -->
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
**Logs**
|
**Logs**
|
||||||
<!-- Please paste any log errors. -->
|
Please paste any log errors.
|
||||||
|
|
||||||
**Screenshots**
|
**Screenshots**
|
||||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**System (please complete the following information):**
|
||||||
|
- OS: [e.g. Docker, Debian, Windows]
|
||||||
|
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||||
|
- Jellyfin Version: [e.g. 10.0.1]
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
<!-- Add any other context about the problem here. -->
|
Add any other context about the problem here.
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Enhancement request
|
||||||
|
about: Suggest an modification to an existing feature
|
||||||
|
title: ''
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest a new feature
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the feature you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
---
|
|
||||||
name: Media playback issue
|
|
||||||
about: Create a media playback issue report
|
|
||||||
title: ''
|
|
||||||
labels: mediaplayback
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Media Info of the file**
|
|
||||||
<!-- Use the Media Info tool (set to text format, download here: https://mediaarea.net/en/MediaInfo) or copy the info from the web ui for the file with the playback issue. -->
|
|
||||||
|
|
||||||
**Logs**
|
|
||||||
<!-- Please paste any log messages from during the playback issue. -->
|
|
||||||
|
|
||||||
**FFmpeg Logs**
|
|
||||||
<!-- Please paste any FFmpeg logs if remuxing or transcoding appears to be part of the issue. -->
|
|
||||||
|
|
||||||
**Stats for Nerds Screenshots**
|
|
||||||
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
|
|
||||||
|
|
||||||
**Server System (please complete the following information):**
|
|
||||||
- OS: [e.g. Docker on Linux, Docker on Windows, Debian, Windows]
|
|
||||||
- Jellyfin Version: [e.g. 10.0.1]
|
|
||||||
- Hardware settings & device: [e.g. NVENC on GTX1060, VAAPI on Intel i7 8700K]
|
|
||||||
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
|
||||||
- Other hardware notes: [e.g. Media mounted in CIFS/SMB share, Media mounted from Google Drive]
|
|
||||||
|
|
||||||
**Client System (please complete the following information):**
|
|
||||||
- Device: [e.g. Apple iPhone XS, Xbox One S, LG OLED55C8, Samsung Galaxy Note9, Custom HTPC]
|
|
||||||
- OS: [e.g. iOS, Android, Windows, macOS]
|
|
||||||
- Client: [e.g. Web/Browser, webOS, Android, Android TV, Electron]
|
|
||||||
- Browser (if Web client): [e.g. Firefox, Chrome, Safari]
|
|
||||||
- Client and Browser Version: [e.g. 10.3.4 and 68.0]
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
<!--
|
|
||||||
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
Ensure your title is short, descriptive, and in the imperative mood (Fix X, Change Y, instead of Fixed X, Changed Y).
|
||||||
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our documentation.
|
For a good inspiration of what to write in commit messages and PRs please review https://chris.beams.io/posts/git-commit/ and our https://jellyfin.readthedocs.io/en/latest/developer-docs/contributing/ page.
|
||||||
-->
|
|
||||||
|
|
||||||
**Changes**
|
**Changes**
|
||||||
<!-- Describe your changes here in 1-5 sentences. -->
|
Describe your changes here in 1-5 sentences.
|
||||||
|
|
||||||
**Issues**
|
**Issues**
|
||||||
<!-- Tag any issues that this PR solves here.
|
Tag any issues that this PR solves here.
|
||||||
ex. Fixes # -->
|
Fixes #
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
# Number of days of inactivity before an issue becomes stale
|
|
||||||
daysUntilStale: 120
|
|
||||||
# Number of days of inactivity before a stale issue is closed
|
|
||||||
daysUntilClose: 21
|
|
||||||
# Issues with these labels will never be considered stale
|
|
||||||
exemptLabels:
|
|
||||||
- regression
|
|
||||||
- security
|
|
||||||
- dotnet-3.0-future
|
|
||||||
- roadmap
|
|
||||||
- future
|
|
||||||
- feature
|
|
||||||
- enhancement
|
|
||||||
- confirmed
|
|
||||||
# Label to use when marking an issue as stale
|
|
||||||
staleLabel: stale
|
|
||||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
||||||
markComment: >
|
|
||||||
This issue has gone 120 days without comment. To avoid abandoned issues, it will be closed in 21 days if there are no new comments.
|
|
||||||
|
|
||||||
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or nightlies, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
|
|
||||||
|
|
||||||
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
|
||||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
|
||||||
closeComment: false
|
|
||||||
+5
-12
@@ -239,6 +239,11 @@ pip-log.txt
|
|||||||
##########
|
##########
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
##########
|
||||||
|
# Visual Studio Code
|
||||||
|
##########
|
||||||
|
.vscode/
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
# Build artifacts
|
# Build artifacts
|
||||||
#########################
|
#########################
|
||||||
@@ -259,15 +264,3 @@ deployment/**/pkg-dist-tmp/
|
|||||||
deployment/collect-dist/
|
deployment/collect-dist/
|
||||||
|
|
||||||
jellyfin_version.ini
|
jellyfin_version.ini
|
||||||
|
|
||||||
ci/
|
|
||||||
|
|
||||||
# Doxygen
|
|
||||||
doc/
|
|
||||||
|
|
||||||
# Deployment artifacts
|
|
||||||
dist
|
|
||||||
*.exe
|
|
||||||
|
|
||||||
# BenchmarkDotNet artifacts
|
|
||||||
BenchmarkDotNet.Artifacts
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "ThirdParty/taglib-sharp"]
|
||||||
|
path = ThirdParty/taglib-sharp
|
||||||
|
url = https://github.com/mono/taglib-sharp
|
||||||
|
[submodule "MediaBrowser.WebDashboard/jellyfin-web"]
|
||||||
|
path = MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
url = https://github.com/jellyfin/jellyfin-web.git
|
||||||
Vendored
+2
-2
@@ -10,7 +10,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"preLaunchTask": "build",
|
"preLaunchTask": "build",
|
||||||
// If you have changed target frameworks, make sure to update the program path.
|
// If you have changed target frameworks, make sure to update the program path.
|
||||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp3.1/jellyfin.dll",
|
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
"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
|
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
|
||||||
@@ -25,4 +25,4 @@
|
|||||||
"processId": "${command:pickProcess}"
|
"processId": "${command:pickProcess}"
|
||||||
}
|
}
|
||||||
,]
|
,]
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+456
@@ -0,0 +1,456 @@
|
|||||||
|
//============================================================================
|
||||||
|
// 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(_fileSystem.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 (_fileSystem.FileExists(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, _fileSystem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, _fileSystem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DirectorySSIF != null)
|
||||||
|
{
|
||||||
|
FileSystemMetadata[] files = GetFiles(DirectorySSIF.FullName, ".ssif").ToArray();
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
InterleavedFiles.Add(
|
||||||
|
file.Name.ToUpper(), new TSInterleavedFile(file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<FileSystemMetadata> GetFiles(string path, string extension)
|
||||||
|
{
|
||||||
|
return _fileSystem.GetFiles(path, new[] { extension }, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetFilePaths(string path, string extension)
|
||||||
|
{
|
||||||
|
return _fileSystem.GetFilePaths(path, new[] { extension }, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Scan()
|
||||||
|
{
|
||||||
|
var errorStreamClipFiles = new List<TSStreamClipFile>();
|
||||||
|
foreach (var streamClipFile in StreamClipFiles.Values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
streamClipFile.Scan();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorStreamClipFiles.Add(streamClipFile);
|
||||||
|
if (StreamClipFileScanError != null)
|
||||||
|
{
|
||||||
|
if (StreamClipFileScanError(streamClipFile, ex))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var streamFile in StreamFiles.Values)
|
||||||
|
{
|
||||||
|
string ssifName = Path.GetFileNameWithoutExtension(streamFile.Name) + ".SSIF";
|
||||||
|
if (InterleavedFiles.ContainsKey(ssifName))
|
||||||
|
{
|
||||||
|
streamFile.InterleavedFile = InterleavedFiles[ssifName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TSStreamFile[] streamFiles = new TSStreamFile[StreamFiles.Count];
|
||||||
|
StreamFiles.Values.CopyTo(streamFiles, 0);
|
||||||
|
Array.Sort(streamFiles, CompareStreamFiles);
|
||||||
|
|
||||||
|
var errorPlaylistFiles = new List<TSPlaylistFile>();
|
||||||
|
foreach (var playlistFile in PlaylistFiles.Values)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
playlistFile.Scan(StreamFiles, StreamClipFiles);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorPlaylistFiles.Add(playlistFile);
|
||||||
|
if (PlaylistFileScanError != null)
|
||||||
|
{
|
||||||
|
if (PlaylistFileScanError(playlistFile, ex))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorStreamFiles = new List<TSStreamFile>();
|
||||||
|
foreach (var streamFile in streamFiles)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var playlists = new List<TSPlaylistFile>();
|
||||||
|
foreach (var playlist in PlaylistFiles.Values)
|
||||||
|
{
|
||||||
|
foreach (var streamClip in playlist.StreamClips)
|
||||||
|
{
|
||||||
|
if (streamClip.Name == streamFile.Name)
|
||||||
|
{
|
||||||
|
playlists.Add(playlist);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
streamFile.Scan(playlists, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
errorStreamFiles.Add(streamFile);
|
||||||
|
if (StreamFileScanError != null)
|
||||||
|
{
|
||||||
|
if (StreamFileScanError(streamFile, ex))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var playlistFile in PlaylistFiles.Values)
|
||||||
|
{
|
||||||
|
playlistFile.Initialize();
|
||||||
|
if (!Is50Hz)
|
||||||
|
{
|
||||||
|
foreach (var videoStream in playlistFile.VideoStreams)
|
||||||
|
{
|
||||||
|
if (videoStream.FrameRate == TSFrameRate.FRAMERATE_25 ||
|
||||||
|
videoStream.FrameRate == TSFrameRate.FRAMERATE_50)
|
||||||
|
{
|
||||||
|
Is50Hz = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileSystemMetadata GetDirectoryBDMV(
|
||||||
|
string path)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(path))
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSystemMetadata dir = _fileSystem.GetDirectoryInfo(path);
|
||||||
|
|
||||||
|
while (dir != null)
|
||||||
|
{
|
||||||
|
if (string.Equals(dir.Name, "BDMV", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
var parentFolder = _fileSystem.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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: The Free Software Media System")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
@@ -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.
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
#undef DEBUG
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
|
namespace BDInfo
|
||||||
|
{
|
||||||
|
public class TSStreamClipFile
|
||||||
|
{
|
||||||
|
private readonly IFileSystem _fileSystem;
|
||||||
|
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, IFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
FileInfo = fileInfo;
|
||||||
|
_fileSystem = fileSystem;
|
||||||
|
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 = _fileSystem.OpenRead(FileInfo.FullName);
|
||||||
|
fileReader = new BinaryReader(fileStream);
|
||||||
|
|
||||||
|
byte[] data = new byte[fileStream.Length];
|
||||||
|
fileReader.Read(data, 0, data.Length);
|
||||||
|
|
||||||
|
byte[] fileType = new byte[8];
|
||||||
|
Array.Copy(data, 0, fileType, 0, fileType.Length);
|
||||||
|
|
||||||
|
FileType = Encoding.ASCII.GetString(fileType, 0, fileType.Length);
|
||||||
|
if (FileType != "HDMV0100" &&
|
||||||
|
FileType != "HDMV0200")
|
||||||
|
{
|
||||||
|
throw new Exception(string.Format(
|
||||||
|
"Clip info file {0} has an unknown file type {1}.",
|
||||||
|
FileInfo.Name, FileType));
|
||||||
|
}
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\tFileType: {0}", FileType));
|
||||||
|
#endif
|
||||||
|
int clipIndex =
|
||||||
|
((int)data[12] << 24) +
|
||||||
|
((int)data[13] << 16) +
|
||||||
|
((int)data[14] << 8) +
|
||||||
|
((int)data[15]);
|
||||||
|
|
||||||
|
int clipLength =
|
||||||
|
((int)data[clipIndex] << 24) +
|
||||||
|
((int)data[clipIndex + 1] << 16) +
|
||||||
|
((int)data[clipIndex + 2] << 8) +
|
||||||
|
((int)data[clipIndex + 3]);
|
||||||
|
|
||||||
|
byte[] clipData = new byte[clipLength];
|
||||||
|
Array.Copy(data, clipIndex + 4, clipData, 0, clipData.Length);
|
||||||
|
|
||||||
|
int streamCount = clipData[8];
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\tStreamCount: {0}", streamCount));
|
||||||
|
#endif
|
||||||
|
int streamOffset = 10;
|
||||||
|
for (int streamIndex = 0;
|
||||||
|
streamIndex < streamCount;
|
||||||
|
streamIndex++)
|
||||||
|
{
|
||||||
|
TSStream stream = null;
|
||||||
|
|
||||||
|
ushort PID = (ushort)
|
||||||
|
((clipData[streamOffset] << 8) +
|
||||||
|
clipData[streamOffset + 1]);
|
||||||
|
|
||||||
|
streamOffset += 2;
|
||||||
|
|
||||||
|
var streamType = (TSStreamType)
|
||||||
|
clipData[streamOffset + 1];
|
||||||
|
switch (streamType)
|
||||||
|
{
|
||||||
|
case TSStreamType.MVC_VIDEO:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TSStreamType.AVC_VIDEO:
|
||||||
|
case TSStreamType.MPEG1_VIDEO:
|
||||||
|
case TSStreamType.MPEG2_VIDEO:
|
||||||
|
case TSStreamType.VC1_VIDEO:
|
||||||
|
{
|
||||||
|
var videoFormat = (TSVideoFormat)
|
||||||
|
(clipData[streamOffset + 2] >> 4);
|
||||||
|
var frameRate = (TSFrameRate)
|
||||||
|
(clipData[streamOffset + 2] & 0xF);
|
||||||
|
var aspectRatio = (TSAspectRatio)
|
||||||
|
(clipData[streamOffset + 3] >> 4);
|
||||||
|
|
||||||
|
stream = new TSVideoStream();
|
||||||
|
((TSVideoStream)stream).VideoFormat = videoFormat;
|
||||||
|
((TSVideoStream)stream).AspectRatio = aspectRatio;
|
||||||
|
((TSVideoStream)stream).FrameRate = frameRate;
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\t{0} {1} {2} {3} {4}",
|
||||||
|
PID,
|
||||||
|
streamType,
|
||||||
|
videoFormat,
|
||||||
|
frameRate,
|
||||||
|
aspectRatio));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TSStreamType.AC3_AUDIO:
|
||||||
|
case TSStreamType.AC3_PLUS_AUDIO:
|
||||||
|
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||||
|
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||||
|
case TSStreamType.DTS_AUDIO:
|
||||||
|
case TSStreamType.DTS_HD_AUDIO:
|
||||||
|
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||||
|
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||||
|
case TSStreamType.LPCM_AUDIO:
|
||||||
|
case TSStreamType.MPEG1_AUDIO:
|
||||||
|
case TSStreamType.MPEG2_AUDIO:
|
||||||
|
{
|
||||||
|
byte[] languageBytes = new byte[3];
|
||||||
|
Array.Copy(clipData, streamOffset + 3,
|
||||||
|
languageBytes, 0, languageBytes.Length);
|
||||||
|
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||||
|
|
||||||
|
var channelLayout = (TSChannelLayout)
|
||||||
|
(clipData[streamOffset + 2] >> 4);
|
||||||
|
var sampleRate = (TSSampleRate)
|
||||||
|
(clipData[streamOffset + 2] & 0xF);
|
||||||
|
|
||||||
|
stream = new TSAudioStream();
|
||||||
|
((TSAudioStream)stream).LanguageCode = languageCode;
|
||||||
|
((TSAudioStream)stream).ChannelLayout = channelLayout;
|
||||||
|
((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
|
||||||
|
((TSAudioStream)stream).LanguageCode = languageCode;
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\t{0} {1} {2} {3} {4}",
|
||||||
|
PID,
|
||||||
|
streamType,
|
||||||
|
languageCode,
|
||||||
|
channelLayout,
|
||||||
|
sampleRate));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||||
|
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||||
|
{
|
||||||
|
byte[] languageBytes = new byte[3];
|
||||||
|
Array.Copy(clipData, streamOffset + 2,
|
||||||
|
languageBytes, 0, languageBytes.Length);
|
||||||
|
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||||
|
|
||||||
|
stream = new TSGraphicsStream();
|
||||||
|
stream.LanguageCode = languageCode;
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\t{0} {1} {2}",
|
||||||
|
PID,
|
||||||
|
streamType,
|
||||||
|
languageCode));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TSStreamType.SUBTITLE:
|
||||||
|
{
|
||||||
|
byte[] languageBytes = new byte[3];
|
||||||
|
Array.Copy(clipData, streamOffset + 3,
|
||||||
|
languageBytes, 0, languageBytes.Length);
|
||||||
|
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||||
|
#if DEBUG
|
||||||
|
Debug.WriteLine(string.Format(
|
||||||
|
"\t{0} {1} {2}",
|
||||||
|
PID,
|
||||||
|
streamType,
|
||||||
|
languageCode));
|
||||||
|
#endif
|
||||||
|
stream = new TSTextStream();
|
||||||
|
stream.LanguageCode = languageCode;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
|
stream.PID = PID;
|
||||||
|
stream.StreamType = streamType;
|
||||||
|
Streams.Add(PID, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamOffset += clipData[streamOffset] + 1;
|
||||||
|
}
|
||||||
|
IsValid = true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (fileReader != null) fileReader.Dispose();
|
||||||
|
if (fileStream != null) fileStream.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
+12
-124
@@ -1,132 +1,20 @@
|
|||||||
# Jellyfin Contributors
|
# Jellyfin Contributors
|
||||||
|
|
||||||
- [97carmine](https://github.com/97carmine)
|
- [JoshuaBoniface](https://github.com/joshuaboniface)
|
||||||
- [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)
|
|
||||||
- [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)
|
|
||||||
- [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)
|
|
||||||
- [dcrdev](https://github.com/dcrdev)
|
|
||||||
- [dhartung](https://github.com/dhartung)
|
|
||||||
- [dinki](https://github.com/dinki)
|
|
||||||
- [dkanada](https://github.com/dkanada)
|
|
||||||
- [dlahoti](https://github.com/dlahoti)
|
|
||||||
- [dmitrylyzo](https://github.com/dmitrylyzo)
|
|
||||||
- [DMouse10462](https://github.com/DMouse10462)
|
|
||||||
- [DrPandemic](https://github.com/DrPandemic)
|
|
||||||
- [EraYaN](https://github.com/EraYaN)
|
|
||||||
- [escabe](https://github.com/escabe)
|
|
||||||
- [excelite](https://github.com/excelite)
|
|
||||||
- [fasheng](https://github.com/fasheng)
|
|
||||||
- [ferferga](https://github.com/ferferga)
|
|
||||||
- [fhriley](https://github.com/fhriley)
|
|
||||||
- [flemse](https://github.com/flemse)
|
|
||||||
- [Froghut](https://github.com/Froghut)
|
|
||||||
- [fruhnow](https://github.com/fruhnow)
|
|
||||||
- [geilername](https://github.com/geilername)
|
|
||||||
- [gnattu](https://github.com/gnattu)
|
|
||||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
|
||||||
- [h1nk](https://github.com/h1nk)
|
|
||||||
- [hawken93](https://github.com/hawken93)
|
|
||||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
|
||||||
- [jftuga](https://github.com/jftuga)
|
|
||||||
- [joern-h](https://github.com/joern-h)
|
|
||||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
|
||||||
- [JustAMan](https://github.com/JustAMan)
|
|
||||||
- [justinfenn](https://github.com/justinfenn)
|
|
||||||
- [KerryRJ](https://github.com/KerryRJ)
|
|
||||||
- [Larvitar](https://github.com/Larvitar)
|
|
||||||
- [LeoVerto](https://github.com/LeoVerto)
|
|
||||||
- [Liggy](https://github.com/Liggy)
|
|
||||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
|
||||||
- [loli10K](https://github.com/loli10K)
|
|
||||||
- [lostmypillow](https://github.com/lostmypillow)
|
|
||||||
- [Lynxy](https://github.com/Lynxy)
|
|
||||||
- [ManfredRichthofen](https://github.com/ManfredRichthofen)
|
|
||||||
- [Marenz](https://github.com/Marenz)
|
|
||||||
- [marius-luca-87](https://github.com/marius-luca-87)
|
|
||||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
|
||||||
- [Matt07211](https://github.com/Matt07211)
|
|
||||||
- [mcarlton00](https://github.com/mcarlton00)
|
|
||||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
|
||||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
|
||||||
- [n8225](https://github.com/n8225)
|
|
||||||
- [Narfinger](https://github.com/Narfinger)
|
|
||||||
- [NathanPickard](https://github.com/NathanPickard)
|
|
||||||
- [neilsb](https://github.com/neilsb)
|
|
||||||
- [nevado](https://github.com/nevado)
|
|
||||||
- [Nickbert7](https://github.com/Nickbert7)
|
|
||||||
- [nvllsvm](https://github.com/nvllsvm)
|
- [nvllsvm](https://github.com/nvllsvm)
|
||||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
- [JustAMan](https://github.com/JustAMan)
|
||||||
- [oddstr13](https://github.com/oddstr13)
|
- [dcrdev](https://github.com/dcrdev)
|
||||||
- [petermcneil](https://github.com/petermcneil)
|
- [EraYaN](https://github.com/EraYaN)
|
||||||
- [Phlogi](https://github.com/Phlogi)
|
- [flemse](https://github.com/flemse)
|
||||||
- [pjeanjean](https://github.com/pjeanjean)
|
- [bfayers](https://github.com/bfayers)
|
||||||
- [ploughpuff](https://github.com/ploughpuff)
|
- [Bond_009](https://github.com/Bond-009)
|
||||||
- [pR0Ps](https://github.com/pR0Ps)
|
- [AnthonyLavado](https://github.com/anthonylavado)
|
||||||
- [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)
|
|
||||||
- [SenorSmartyPants](https://github.com/SenorSmartyPants)
|
|
||||||
- [shemanaev](https://github.com/shemanaev)
|
|
||||||
- [skaro13](https://github.com/skaro13)
|
|
||||||
- [sl1288](https://github.com/sl1288)
|
|
||||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
|
||||||
- [sparky8251](https://github.com/sparky8251)
|
- [sparky8251](https://github.com/sparky8251)
|
||||||
- [stanionascu](https://github.com/stanionascu)
|
- [LeoVerto](https://github.com/LeoVerto)
|
||||||
- [stevehayles](https://github.com/stevehayles)
|
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
- [cvium](https://github.com/cvium)
|
||||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
|
||||||
- [teacupx](https://github.com/teacupx)
|
|
||||||
- [Terror-Gene](https://github.com/Terror-Gene)
|
|
||||||
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
|
|
||||||
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
|
|
||||||
- [thornbill](https://github.com/thornbill)
|
|
||||||
- [ThreeFive-O](https://github.com/ThreeFive-O)
|
|
||||||
- [TrisMcC](https://github.com/TrisMcC)
|
|
||||||
- [trumblejoe](https://github.com/trumblejoe)
|
|
||||||
- [TtheCreator](https://github.com/TtheCreator)
|
|
||||||
- [twinkybot](https://github.com/twinkybot)
|
|
||||||
- [Ullmie02](https://github.com/Ullmie02)
|
|
||||||
- [Unhelpful](https://github.com/Unhelpful)
|
|
||||||
- [viaregio](https://github.com/viaregio)
|
|
||||||
- [vitorsemeano](https://github.com/vitorsemeano)
|
|
||||||
- [voodoos](https://github.com/voodoos)
|
|
||||||
- [whooo](https://github.com/whooo)
|
|
||||||
- [WiiPlayer2](https://github.com/WiiPlayer2)
|
|
||||||
- [WillWill56](https://github.com/WillWill56)
|
|
||||||
- [wtayl0r](https://github.com/wtayl0r)
|
- [wtayl0r](https://github.com/wtayl0r)
|
||||||
- [Wuerfelbecher](https://github.com/Wuerfelbecher)
|
- [TtheCreator](https://github.com/Tthecreator)
|
||||||
- [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)
|
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|||||||
+27
-54
@@ -1,64 +1,37 @@
|
|||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=2
|
||||||
ARG FFMPEG_VERSION=latest
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
|
||||||
ARG JELLYFIN_WEB_VERSION=10.5.2
|
|
||||||
RUN apk add curl git \
|
|
||||||
&& git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
|
|
||||||
&& cd jellyfin-web \
|
|
||||||
&& yarn install \
|
|
||||||
&& yarn build \
|
|
||||||
&& mv dist /dist
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION}-buster as builder
|
# Download ffmpeg first to allow quicker rebuild of other layers
|
||||||
|
FROM alpine as ffmpeg
|
||||||
|
ARG FFMPEG_URL=https://www.johnvansickle.com/ffmpeg/old-releases/ffmpeg-4.0.3-64bit-static.tar.xz
|
||||||
|
RUN wget ${FFMPEG_URL} -O - | tar Jxf - \
|
||||||
|
&& mkdir ffmpeg-bin \
|
||||||
|
&& mv ffmpeg*/ffmpeg ffmpeg-bin \
|
||||||
|
&& mv ffmpeg*/ffprobe ffmpeg-bin
|
||||||
|
|
||||||
|
|
||||||
|
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk as builder
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
&& dotnet clean \
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
&& dotnet publish \
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
--configuration release \
|
||||||
|
--output /jellyfin \
|
||||||
|
Jellyfin.Server
|
||||||
|
|
||||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
|
||||||
FROM debian:buster-slim
|
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime
|
||||||
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=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=ffmpeg /ffmpeg-bin/* /usr/bin/
|
||||||
# Install dependencies:
|
EXPOSE 8096
|
||||||
# libfontconfig1: needed for Skia
|
VOLUME /config /media
|
||||||
# libgomp1: needed for ffmpeg
|
|
||||||
# libva-drm2: needed for ffmpeg
|
# libfontconfig1 is required for Skia
|
||||||
# mesa-va-drivers: needed for VAAPI
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
libfontconfig1 \
|
libfontconfig1 \
|
||||||
libgomp1 \
|
&& apt-get clean autoclean \
|
||||||
libva-drm2 \
|
&& apt-get autoremove \
|
||||||
mesa-va-drivers \
|
&& rm -rf /var/lib/{apt,dpkg,cache,log}
|
||||||
openssl \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||||
ca-certificates \
|
|
||||||
vainfo \
|
|
||||||
i965-va-driver \
|
|
||||||
&& 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 \
|
|
||||||
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
|
|
||||||
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
|
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
|
||||||
|
|
||||||
EXPOSE 8096
|
|
||||||
VOLUME /cache /config /media
|
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
|
||||||
"--datadir", "/config", \
|
|
||||||
"--cachedir", "/cache", \
|
|
||||||
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
|
||||||
|
|||||||
+16
-64
@@ -1,72 +1,24 @@
|
|||||||
# DESIGNED FOR BUILDING ON AMD64 ONLY
|
ARG DOTNET_VERSION=3.0
|
||||||
#####################################
|
|
||||||
# Requires binfm_misc registration
|
|
||||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
|
||||||
ARG DOTNET_VERSION=3.1
|
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm32v7 as builder
|
||||||
ARG JELLYFIN_WEB_VERSION=10.5.2
|
|
||||||
RUN apk add curl git \
|
|
||||||
&& git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
|
|
||||||
&& cd jellyfin-web \
|
|
||||||
&& yarn install \
|
|
||||||
&& yarn build \
|
|
||||||
&& mv dist /dist
|
|
||||||
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
|
||||||
WORKDIR /repo
|
WORKDIR /repo
|
||||||
COPY . .
|
COPY . .
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
#TODO Remove or update the sed line when we update dotnet version.
|
||||||
# Discard objs - may cause failures if exists
|
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||||
RUN find . -type d -name obj | xargs -r rm -r
|
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
|
||||||
# Build
|
&& dotnet clean -maxcpucount:1 \
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
&& dotnet publish \
|
||||||
|
-maxcpucount:1 \
|
||||||
|
--configuration release \
|
||||||
|
--output /jellyfin \
|
||||||
|
Jellyfin.Server
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm32v7
|
||||||
FROM arm32v7/debian:buster-slim
|
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
|
||||||
ARG DEBIAN_FRONTEND="noninteractive"
|
|
||||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
|
||||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
|
||||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
|
||||||
|
|
||||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
|
|
||||||
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
|
|
||||||
curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
|
|
||||||
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
|
|
||||||
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install --no-install-recommends --no-install-suggests -y \
|
|
||||||
jellyfin-ffmpeg \
|
|
||||||
libssl-dev \
|
|
||||||
libfontconfig1 \
|
|
||||||
libfreetype6 \
|
|
||||||
libomxil-bellagio0 \
|
|
||||||
libomxil-bellagio-bin \
|
|
||||||
libraspberrypi0 \
|
|
||||||
vainfo \
|
|
||||||
libva2 \
|
|
||||||
&& apt-get remove curl 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
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
RUN apt-get update \
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
&& apt-get install -y ffmpeg
|
||||||
"--datadir", "/config", \
|
VOLUME /config /media
|
||||||
"--cachedir", "/cache", \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
|
||||||
|
|||||||
+25
-53
@@ -1,61 +1,33 @@
|
|||||||
# DESIGNED FOR BUILDING ON AMD64 ONLY
|
# Requires binfm_misc registration for aarch64
|
||||||
#####################################
|
|
||||||
# Requires binfm_misc registration
|
|
||||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.0
|
||||||
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
|
||||||
ARG JELLYFIN_WEB_VERSION=10.5.2
|
|
||||||
RUN apk add curl git \
|
|
||||||
&& git clone --branch release-10.5.z --single-branch https://github.com/jellyfin/jellyfin-web.git \
|
|
||||||
&& cd jellyfin-web \
|
|
||||||
&& yarn install \
|
|
||||||
&& yarn build \
|
|
||||||
&& mv dist /dist
|
|
||||||
|
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
|
||||||
WORKDIR /repo
|
|
||||||
COPY . .
|
|
||||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
|
||||||
# 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:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||||
FROM arm64v8/debian:buster-slim
|
FROM alpine as qemu_extract
|
||||||
|
COPY --from=qemu /usr/bin qemu_user_static.tgz
|
||||||
|
RUN tar -xzvf qemu_user_static.tgz
|
||||||
|
|
||||||
# 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
|
FROM microsoft/dotnet:${DOTNET_VERSION}-sdk-stretch-arm64v8 as builder
|
||||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
|
COPY --from=qemu_extract qemu-* /usr/bin
|
||||||
ffmpeg \
|
WORKDIR /repo
|
||||||
libssl-dev \
|
COPY . .
|
||||||
ca-certificates \
|
#TODO Remove or update the sed line when we update dotnet version.
|
||||||
libfontconfig1 \
|
RUN export DOTNET_CLI_TELEMETRY_OPTOUT=1 \
|
||||||
libfreetype6 \
|
&& find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \; \
|
||||||
libomxil-bellagio0 \
|
&& dotnet clean \
|
||||||
libomxil-bellagio-bin \
|
&& dotnet publish \
|
||||||
&& apt-get clean autoclean -y \
|
--configuration release \
|
||||||
&& apt-get autoremove -y \
|
--output /jellyfin \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
Jellyfin.Server
|
||||||
&& mkdir -p /cache /config /media \
|
|
||||||
&& chmod 777 /cache /config /media
|
|
||||||
|
FROM microsoft/dotnet:${DOTNET_VERSION}-runtime-stretch-slim-arm64v8
|
||||||
|
COPY --from=qemu_extract qemu-* /usr/bin
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
RUN apt-get update \
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
&& apt-get install -y ffmpeg
|
||||||
"--datadir", "/config", \
|
VOLUME /config /media
|
||||||
"--cachedir", "/cache", \
|
ENTRYPOINT dotnet /jellyfin/jellyfin.dll -programdata /config
|
||||||
"--ffmpeg", "/usr/bin/ffmpeg"]
|
|
||||||
|
|||||||
@@ -9,9 +9,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
namespace DvdLib.Ifo
|
||||||
|
{
|
||||||
|
public enum AudioCodec
|
||||||
|
{
|
||||||
|
AC3 = 0,
|
||||||
|
MPEG1 = 2,
|
||||||
|
MPEG2ext = 3,
|
||||||
|
LPCM = 4,
|
||||||
|
DTS = 6,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ApplicationMode
|
||||||
|
{
|
||||||
|
Unspecified = 0,
|
||||||
|
Karaoke = 1,
|
||||||
|
Surround = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AudioAttributes
|
||||||
|
{
|
||||||
|
public readonly AudioCodec Codec;
|
||||||
|
public readonly bool MultichannelExtensionPresent;
|
||||||
|
public readonly ApplicationMode Mode;
|
||||||
|
public readonly byte QuantDRC;
|
||||||
|
public readonly byte SampleRate;
|
||||||
|
public readonly byte Channels;
|
||||||
|
public readonly ushort LanguageCode;
|
||||||
|
public readonly byte LanguageExtension;
|
||||||
|
public readonly byte CodeExtension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MultiChannelExtension
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
-11
@@ -26,23 +26,23 @@ namespace DvdLib.Ifo
|
|||||||
|
|
||||||
if (vmgPath == null)
|
if (vmgPath == null)
|
||||||
{
|
{
|
||||||
foreach (var ifo in allFiles)
|
var allIfos = allFiles.Where(i => string.Equals(i.Extension, ".ifo", StringComparison.OrdinalIgnoreCase));
|
||||||
{
|
|
||||||
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
foreach (var ifo in allIfos)
|
||||||
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
{
|
||||||
|
var num = ifo.Name.Split('_').ElementAtOrDefault(1);
|
||||||
|
var numbersRead = new List<ushort>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(num) && ushort.TryParse(num, out var ifoNumber) && !numbersRead.Contains(ifoNumber))
|
||||||
{
|
{
|
||||||
ReadVTS(ifoNumber, ifo.FullName);
|
ReadVTS(ifoNumber, ifo.FullName);
|
||||||
|
numbersRead.Add(ifoNumber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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))
|
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
||||||
{
|
{
|
||||||
@@ -76,7 +76,7 @@ namespace DvdLib.Ifo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
private void ReadVTS(ushort vtsNum, List<FileSystemMetadata> allFiles)
|
||||||
{
|
{
|
||||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ namespace DvdLib.Ifo
|
|||||||
{
|
{
|
||||||
VTSPaths[vtsNum] = vtsPath;
|
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))
|
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DvdLib.Ifo
|
||||||
|
{
|
||||||
|
public class ProgramChainCommandTable
|
||||||
|
{
|
||||||
|
public readonly ushort LastByteAddress;
|
||||||
|
public readonly List<VirtualMachineCommand> PreCommands;
|
||||||
|
public readonly List<VirtualMachineCommand> PostCommands;
|
||||||
|
public readonly List<VirtualMachineCommand> CellCommands;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VirtualMachineCommand
|
||||||
|
{
|
||||||
|
public readonly byte[] Command;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,10 +25,13 @@ namespace DvdLib.Ifo
|
|||||||
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
||||||
|
|
||||||
private ushort _nextProgramNumber;
|
private ushort _nextProgramNumber;
|
||||||
|
public readonly ProgramChain Next;
|
||||||
|
|
||||||
private ushort _prevProgramNumber;
|
private ushort _prevProgramNumber;
|
||||||
|
public readonly ProgramChain Previous;
|
||||||
|
|
||||||
private ushort _goupProgramNumber;
|
private ushort _goupProgramNumber;
|
||||||
|
public readonly ProgramChain Goup; // ?? maybe Group
|
||||||
|
|
||||||
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
||||||
public uint ProgramCount { get; private set; }
|
public uint ProgramCount { get; private set; }
|
||||||
@@ -37,6 +40,7 @@ namespace DvdLib.Ifo
|
|||||||
public byte[] Palette { get; private set; } // 16*4 entries
|
public byte[] Palette { get; private set; } // 16*4 entries
|
||||||
|
|
||||||
private ushort _commandTableOffset;
|
private ushort _commandTableOffset;
|
||||||
|
public readonly ProgramChainCommandTable CommandTable;
|
||||||
|
|
||||||
private ushort _programMapOffset;
|
private ushort _programMapOffset;
|
||||||
private ushort _cellPlaybackOffset;
|
private ushort _cellPlaybackOffset;
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
namespace DvdLib.Ifo
|
||||||
|
{
|
||||||
|
public enum VideoCodec
|
||||||
|
{
|
||||||
|
MPEG1 = 0,
|
||||||
|
MPEG2 = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum VideoFormat
|
||||||
|
{
|
||||||
|
NTSC = 0,
|
||||||
|
PAL = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum AspectRatio
|
||||||
|
{
|
||||||
|
ar4to3 = 0,
|
||||||
|
ar16to9 = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum FilmMode
|
||||||
|
{
|
||||||
|
None = -1,
|
||||||
|
Camera = 0,
|
||||||
|
Film = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VideoAttributes
|
||||||
|
{
|
||||||
|
public readonly VideoCodec Codec;
|
||||||
|
public readonly VideoFormat Format;
|
||||||
|
public readonly AspectRatio Aspect;
|
||||||
|
public readonly bool AutomaticPanScan;
|
||||||
|
public readonly bool AutomaticLetterBox;
|
||||||
|
public readonly bool Line21CCField1;
|
||||||
|
public readonly bool Line21CCField2;
|
||||||
|
public readonly int Width;
|
||||||
|
public readonly int Height;
|
||||||
|
public readonly bool Letterboxed;
|
||||||
|
public readonly FilmMode FilmMode;
|
||||||
|
|
||||||
|
public VideoAttributes()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,8 +9,8 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
[assembly: AssemblyProduct("Jellyfin: The Free Software Media System")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License Version 2")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: NeutralResourcesLanguage("en")]
|
[assembly: NeutralResourcesLanguage("en")]
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
@@ -110,13 +108,12 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
public class DlnaServerService : IService, IRequiresRequest
|
public class DlnaServerService : IService, IRequiresRequest
|
||||||
{
|
{
|
||||||
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
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; }
|
public IRequest Request { get; set; }
|
||||||
|
private IHttpResultFactory _resultFactory;
|
||||||
|
|
||||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
||||||
|
|
||||||
@@ -124,14 +121,10 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
||||||
|
|
||||||
public DlnaServerService(
|
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
|
||||||
IDlnaManager dlnaManager,
|
|
||||||
IHttpResultFactory httpResultFactory,
|
|
||||||
IServerConfigurationManager configurationManager)
|
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_resultFactory = httpResultFactory;
|
_resultFactory = httpResultFactory;
|
||||||
_configurationManager = configurationManager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHeader(string name)
|
private string GetHeader(string name)
|
||||||
@@ -143,7 +136,7 @@ namespace Emby.Dlna.Api
|
|||||||
{
|
{
|
||||||
var url = Request.AbsoluteUri;
|
var url = Request.AbsoluteUri;
|
||||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
|
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers.ToDictionary(), request.UuId, serverAddress);
|
||||||
|
|
||||||
var cacheLength = TimeSpan.FromDays(1);
|
var cacheLength = TimeSpan.FromDays(1);
|
||||||
var cacheKey = Request.RawUrl.GetMD5();
|
var cacheKey = Request.RawUrl.GetMD5();
|
||||||
@@ -154,159 +147,96 @@ namespace Emby.Dlna.Api
|
|||||||
|
|
||||||
public object Get(GetContentDirectory request)
|
public object Get(GetContentDirectory request)
|
||||||
{
|
{
|
||||||
var xml = ContentDirectory.GetServiceXml();
|
var xml = ContentDirectory.GetServiceXml(Request.Headers.ToDictionary());
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetMediaReceiverRegistrar request)
|
public object Get(GetMediaReceiverRegistrar request)
|
||||||
{
|
{
|
||||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
var xml = MediaReceiverRegistrar.GetServiceXml(Request.Headers.ToDictionary());
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetConnnectionManager request)
|
public object Get(GetConnnectionManager request)
|
||||||
{
|
{
|
||||||
var xml = ConnectionManager.GetServiceXml();
|
var xml = ConnectionManager.GetServiceXml(Request.Headers.ToDictionary());
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
|
public object Post(ProcessMediaReceiverRegistrarControlRequest request)
|
||||||
{
|
{
|
||||||
var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
|
var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
|
public object Post(ProcessContentDirectoryControlRequest request)
|
||||||
{
|
{
|
||||||
var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
|
var response = PostAsync(request.RequestStream, ContentDirectory);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
|
public object Post(ProcessConnectionManagerControlRequest request)
|
||||||
{
|
{
|
||||||
var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
|
var response = PostAsync(request.RequestStream, ConnectionManager);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
|
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
|
||||||
{
|
{
|
||||||
var id = GetPathValue(2).ToString();
|
var id = GetPathValue(2);
|
||||||
|
|
||||||
return service.ProcessControlRequestAsync(new ControlRequest
|
return service.ProcessControlRequest(new ControlRequest
|
||||||
{
|
{
|
||||||
Headers = Request.Headers,
|
Headers = Request.Headers.ToDictionary(),
|
||||||
InputXml = requestStream,
|
InputXml = requestStream,
|
||||||
TargetServerUuId = id,
|
TargetServerUuId = id,
|
||||||
RequestedUrl = Request.AbsoluteUri
|
RequestedUrl = Request.AbsoluteUri
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from MediaBrowser.Api/BaseApiService.cs
|
protected string GetPathValue(int index)
|
||||||
// TODO: Remove code duplication
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the path segment at the specified index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">The index of the path segment.</param>
|
|
||||||
/// <returns>The path segment at the specified index.</returns>
|
|
||||||
/// <exception cref="IndexOutOfRangeException" >Path doesn't contain enough segments.</exception>
|
|
||||||
/// <exception cref="InvalidDataException" >Path doesn't start with the base url.</exception>
|
|
||||||
protected internal ReadOnlySpan<char> GetPathValue(int index)
|
|
||||||
{
|
{
|
||||||
static void ThrowIndexOutOfRangeException()
|
var pathInfo = Parse(Request.PathInfo);
|
||||||
=> throw new IndexOutOfRangeException("Path doesn't contain enough segments.");
|
var first = pathInfo[0];
|
||||||
|
|
||||||
static void ThrowInvalidDataException()
|
// backwards compatibility
|
||||||
=> throw new InvalidDataException("Path doesn't start with the base url.");
|
// TODO: Work out what this is doing.
|
||||||
|
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
|
||||||
ReadOnlySpan<char> path = Request.PathInfo;
|
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
|
||||||
// Remove the protocol part from the url
|
|
||||||
int pos = path.LastIndexOf("://");
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
{
|
||||||
path = path.Slice(pos + 3);
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the query string
|
return pathInfo[index];
|
||||||
pos = path.LastIndexOf('?');
|
}
|
||||||
if (pos != -1)
|
|
||||||
|
private List<string> Parse(string pathUri)
|
||||||
|
{
|
||||||
|
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
||||||
|
|
||||||
|
var pathInfo = actionParts[actionParts.Length - 1];
|
||||||
|
|
||||||
|
var optionsPos = pathInfo.LastIndexOf('?');
|
||||||
|
if (optionsPos != -1)
|
||||||
{
|
{
|
||||||
path = path.Slice(0, pos);
|
pathInfo = pathInfo.Substring(0, optionsPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the domain
|
var args = pathInfo.Split('/');
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove base url
|
return args.Skip(1).ToList();
|
||||||
string baseUrl = _configurationManager.Configuration.BaseUrl;
|
|
||||||
int baseUrlLen = baseUrl.Length;
|
|
||||||
if (baseUrlLen != 0)
|
|
||||||
{
|
|
||||||
if (path.StartsWith(baseUrl, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(baseUrlLen);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// The path doesn't start with the base url,
|
|
||||||
// how did we get here?
|
|
||||||
ThrowInvalidDataException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove leading /
|
|
||||||
path = path.Slice(1);
|
|
||||||
|
|
||||||
// Backwards compatibility
|
|
||||||
const string Emby = "emby/";
|
|
||||||
if (path.StartsWith(Emby, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(Emby.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
const string MediaBrowser = "mediabrowser/";
|
|
||||||
if (path.StartsWith(MediaBrowser, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
path = path.Slice(MediaBrowser.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip segments until we are at the right index
|
|
||||||
for (int i = 0; i < index; i++)
|
|
||||||
{
|
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos == -1)
|
|
||||||
{
|
|
||||||
ThrowIndexOutOfRangeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
path = path.Slice(pos + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the rest
|
|
||||||
pos = path.IndexOf('/');
|
|
||||||
if (pos != -1)
|
|
||||||
{
|
|
||||||
path = path.Slice(0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Get(GetIcon request)
|
public object Get(GetIcon request)
|
||||||
{
|
{
|
||||||
var contentType = "image/" + Path.GetExtension(request.Filename)
|
var contentType = "image/" + Path.GetExtension(request.Filename).TrimStart('.').ToLower();
|
||||||
.TrimStart('.')
|
|
||||||
.ToLowerInvariant();
|
|
||||||
|
|
||||||
var cacheLength = TimeSpan.FromDays(365);
|
var cacheLength = TimeSpan.FromDays(365);
|
||||||
var cacheKey = Request.RawUrl.GetMD5();
|
var cacheKey = Request.RawUrl.GetMD5();
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Globalization;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
@@ -17,14 +13,9 @@ namespace Emby.Dlna.Common
|
|||||||
|
|
||||||
public string Depth { get; set; }
|
public string Depth { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return string.Format(
|
return string.Format("{0}x{1}", Height, Width);
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"{0}x{1}",
|
|
||||||
Height,
|
|
||||||
Width);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
@@ -15,8 +13,9 @@ namespace Emby.Dlna.Common
|
|||||||
|
|
||||||
public string EventSubUrl { get; set; }
|
public string EventSubUrl { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> ServiceId;
|
{
|
||||||
|
return string.Format("{0}", ServiceId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
public class ServiceAction
|
public class ServiceAction
|
||||||
{
|
{
|
||||||
public ServiceAction()
|
|
||||||
{
|
|
||||||
ArgumentList = new List<Argument>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public List<Argument> ArgumentList { get; set; }
|
public List<Argument> ArgumentList { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return Name;
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceAction()
|
||||||
|
{
|
||||||
|
ArgumentList = new List<Argument>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Emby.Dlna.Common
|
namespace Emby.Dlna.Common
|
||||||
{
|
{
|
||||||
public class StateVariable
|
public class StateVariable
|
||||||
{
|
{
|
||||||
public StateVariable()
|
|
||||||
{
|
|
||||||
AllowedValues = Array.Empty<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
public string DataType { get; set; }
|
public string DataType { get; set; }
|
||||||
@@ -20,8 +12,14 @@ namespace Emby.Dlna.Common
|
|||||||
|
|
||||||
public string[] AllowedValues { get; set; }
|
public string[] AllowedValues { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
=> Name;
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StateVariable()
|
||||||
|
{
|
||||||
|
AllowedValues = Array.Empty<string>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,23 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Configuration
|
namespace Emby.Dlna.Configuration
|
||||||
{
|
{
|
||||||
public class DlnaOptions
|
public class DlnaOptions
|
||||||
{
|
{
|
||||||
|
public bool EnablePlayTo { get; set; }
|
||||||
|
public bool EnableServer { get; set; }
|
||||||
|
public bool EnableDebugLog { get; set; }
|
||||||
|
public bool BlastAliveMessages { get; set; }
|
||||||
|
public int ClientDiscoveryIntervalSeconds { get; set; }
|
||||||
|
public int BlastAliveMessageIntervalSeconds { get; set; }
|
||||||
|
public string DefaultUserId { get; set; }
|
||||||
|
|
||||||
public DlnaOptions()
|
public DlnaOptions()
|
||||||
{
|
{
|
||||||
EnablePlayTo = true;
|
EnablePlayTo = true;
|
||||||
EnableServer = true;
|
EnableServer = true;
|
||||||
BlastAliveMessages = true;
|
BlastAliveMessages = true;
|
||||||
SendOnlyMatchedHost = true;
|
|
||||||
ClientDiscoveryIntervalSeconds = 60;
|
ClientDiscoveryIntervalSeconds = 60;
|
||||||
BlastAliveMessageIntervalSeconds = 1800;
|
BlastAliveMessageIntervalSeconds = 1800;
|
||||||
}
|
}
|
||||||
|
|
||||||
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; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Configuration;
|
using Emby.Dlna.Configuration;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#pragma warning disable CS1591
|
using System.Collections.Generic;
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ConnectionManager
|
namespace Emby.Dlna.ConnectionManager
|
||||||
@@ -15,28 +13,28 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
private readonly IDlnaManager _dlna;
|
private readonly IDlnaManager _dlna;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||||
|
|
||||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
|
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
_config = config;
|
_config = config;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public string GetServiceXml(IDictionary<string, string> headers)
|
||||||
public string GetServiceXml()
|
|
||||||
{
|
{
|
||||||
return new ConnectionManagerXmlBuilder().GetXml();
|
return new ConnectionManagerXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
|
||||||
{
|
{
|
||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
|
return new ControlHandler(_config, _logger, XmlReaderSettingsFactory, profile).ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Xml;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ConnectionManager
|
namespace Emby.Dlna.ConnectionManager
|
||||||
@@ -16,28 +13,28 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
{
|
{
|
||||||
private readonly DeviceProfile _profile;
|
private readonly DeviceProfile _profile;
|
||||||
|
|
||||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
|
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||||
: base(config, logger)
|
|
||||||
{
|
|
||||||
_profile = profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
HandleGetProtocolInfo(xmlWriter);
|
return HandleGetProtocolInfo();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetProtocolInfo(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString("Source", _profile.ProtocolInfo);
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
xmlWriter.WriteElementString("Sink", string.Empty);
|
{
|
||||||
|
{ "Source", _profile.ProtocolInfo },
|
||||||
|
{ "Sink", "" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory, DeviceProfile profile) : base(config, logger, xmlReaderSettingsFactory)
|
||||||
|
{
|
||||||
|
_profile = profile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@@ -14,6 +11,7 @@ using MediaBrowser.Controller.MediaEncoding;
|
|||||||
using MediaBrowser.Controller.TV;
|
using MediaBrowser.Controller.TV;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
@@ -30,6 +28,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IUserViewManager _userViewManager;
|
private readonly IUserViewManager _userViewManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||||
private readonly ITVSeriesManager _tvSeriesManager;
|
private readonly ITVSeriesManager _tvSeriesManager;
|
||||||
|
|
||||||
public ContentDirectory(IDlnaManager dlna,
|
public ContentDirectory(IDlnaManager dlna,
|
||||||
@@ -39,12 +38,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
||||||
ILocalizationManager localization,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IUserViewManager userViewManager,
|
|
||||||
IMediaEncoder mediaEncoder,
|
|
||||||
ITVSeriesManager tvSeriesManager)
|
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_dlna = dlna;
|
_dlna = dlna;
|
||||||
@@ -57,6 +51,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_userViewManager = userViewManager;
|
_userViewManager = userViewManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||||
_tvSeriesManager = tvSeriesManager;
|
_tvSeriesManager = tvSeriesManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,19 +65,18 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public string GetServiceXml(IDictionary<string, string> headers)
|
||||||
public string GetServiceXml()
|
|
||||||
{
|
{
|
||||||
return new ContentDirectoryXmlBuilder().GetXml();
|
return new ContentDirectoryXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
|
||||||
{
|
{
|
||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||||
|
string accessToken = null;
|
||||||
|
|
||||||
var user = GetUser(profile);
|
var user = GetUser(profile);
|
||||||
|
|
||||||
@@ -91,7 +85,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_libraryManager,
|
_libraryManager,
|
||||||
profile,
|
profile,
|
||||||
serverAddress,
|
serverAddress,
|
||||||
null,
|
accessToken,
|
||||||
_imageProcessor,
|
_imageProcessor,
|
||||||
_userDataManager,
|
_userDataManager,
|
||||||
user,
|
user,
|
||||||
@@ -101,15 +95,16 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_mediaSourceManager,
|
_mediaSourceManager,
|
||||||
_userViewManager,
|
_userViewManager,
|
||||||
_mediaEncoder,
|
_mediaEncoder,
|
||||||
|
XmlReaderSettingsFactory,
|
||||||
_tvSeriesManager)
|
_tvSeriesManager)
|
||||||
.ProcessControlRequestAsync(request);
|
.ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private User GetUser(DeviceProfile profile)
|
private User GetUser(DeviceProfile profile)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(profile.UserId))
|
if (!string.IsNullOrEmpty(profile.UserId))
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(Guid.Parse(profile.UserId));
|
var user = _userManager.GetUserById(profile.UserId);
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
@@ -121,7 +116,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(userId))
|
if (!string.IsNullOrEmpty(userId))
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(Guid.Parse(userId));
|
var user = _userManager.GetUserById(userId);
|
||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -28,6 +25,7 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.ContentDirectory
|
namespace Emby.Dlna.ContentDirectory
|
||||||
@@ -47,28 +45,14 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
private const string NS_UPNP = "urn:schemas-upnp-org:metadata-1-0/upnp/";
|
||||||
|
|
||||||
private readonly int _systemUpdateId;
|
private readonly int _systemUpdateId;
|
||||||
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private readonly DidlBuilder _didlBuilder;
|
private readonly DidlBuilder _didlBuilder;
|
||||||
|
|
||||||
private readonly DeviceProfile _profile;
|
private readonly DeviceProfile _profile;
|
||||||
|
|
||||||
public ControlHandler(
|
public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager, IMediaEncoder mediaEncoder, IXmlReaderSettingsFactory xmlReaderSettingsFactory, ITVSeriesManager tvSeriesManager)
|
||||||
ILogger logger,
|
: base(config, logger, xmlReaderSettingsFactory)
|
||||||
ILibraryManager libraryManager,
|
|
||||||
DeviceProfile profile,
|
|
||||||
string serverAddress,
|
|
||||||
string accessToken,
|
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
IUserDataManager userDataManager,
|
|
||||||
User user,
|
|
||||||
int systemUpdateId,
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
ILocalizationManager localization,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IUserViewManager userViewManager,
|
|
||||||
IMediaEncoder mediaEncoder,
|
|
||||||
ITVSeriesManager tvSeriesManager)
|
|
||||||
: base(config, logger)
|
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@@ -79,132 +63,117 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
_profile = profile;
|
_profile = profile;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
|
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, _logger, libraryManager, mediaEncoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||||
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
|
|
||||||
{
|
{
|
||||||
const string DeviceId = "test";
|
var deviceId = "test";
|
||||||
|
|
||||||
|
var user = _user;
|
||||||
|
|
||||||
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleGetSearchCapabilities();
|
||||||
HandleGetSearchCapabilities(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleGetSortCapabilities();
|
||||||
HandleGetSortCapabilities(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleGetSortExtensionCapabilities();
|
||||||
HandleGetSortExtensionCapabilities(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleGetSystemUpdateID();
|
||||||
HandleGetSystemUpdateID(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleBrowse(methodParams, user, deviceId);
|
||||||
HandleBrowse(xmlWriter, methodParams, DeviceId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleXGetFeatureList();
|
||||||
HandleXGetFeatureList(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleGetFeatureList();
|
||||||
HandleGetFeatureList(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleXSetBookmark(methodParams, user);
|
||||||
HandleXSetBookmark(methodParams);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleSearch(methodParams, user, deviceId);
|
||||||
HandleSearch(xmlWriter, methodParams, DeviceId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleX_BrowseByLetter(methodParams, user, deviceId);
|
||||||
HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleXSetBookmark(IDictionary<string, string> sparams)
|
private IEnumerable<KeyValuePair<string, string>> HandleXSetBookmark(IDictionary<string, string> sparams, User user)
|
||||||
{
|
{
|
||||||
var id = sparams["ObjectID"];
|
var id = sparams["ObjectID"];
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(id, _user);
|
var serverItem = GetItemFromObjectId(id, user);
|
||||||
|
|
||||||
var item = serverItem.Item;
|
var item = serverItem.Item;
|
||||||
|
|
||||||
var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture);
|
var newbookmark = int.Parse(sparams["PosSecond"], _usCulture);
|
||||||
|
|
||||||
var userdata = _userDataManager.GetUserData(_user, item);
|
var userdata = _userDataManager.GetUserData(user, item);
|
||||||
|
|
||||||
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
|
userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
|
||||||
|
|
||||||
_userDataManager.SaveUserData(_user, item, userdata, UserDataSaveReason.TogglePlayed,
|
_userDataManager.SaveUserData(user, item, userdata, UserDataSaveReason.TogglePlayed,
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetSearchCapabilities(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetSearchCapabilities()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString(
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
"SearchCaps",
|
{
|
||||||
"res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords");
|
{ "SearchCaps", "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords" }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetSortCapabilities(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetSortCapabilities()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString(
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
"SortCaps",
|
{
|
||||||
"res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
|
{ "SortCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetSortExtensionCapabilities()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString(
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
"SortExtensionCaps",
|
{
|
||||||
"res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
|
{ "SortExtensionCaps", "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating" }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetSystemUpdateID()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
headers.Add("Id", _systemUpdateId.ToString(_usCulture));
|
||||||
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleGetFeatureList(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleGetFeatureList()
|
||||||
{
|
{
|
||||||
xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "FeatureList", GetFeatureListXml() }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleXGetFeatureList(XmlWriter xmlWriter)
|
private IEnumerable<KeyValuePair<string, string>> HandleXGetFeatureList()
|
||||||
=> HandleGetFeatureList(xmlWriter);
|
{
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
private string WriteFeatureListXml()
|
{
|
||||||
|
{ "FeatureList", GetFeatureListXml() }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFeatureListXml()
|
||||||
{
|
{
|
||||||
// TODO: clean this up
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
||||||
@@ -231,7 +200,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleBrowse(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
private IEnumerable<KeyValuePair<string, string>> HandleBrowse(IDictionary<string, string> sparams, User user, string deviceId)
|
||||||
{
|
{
|
||||||
var id = sparams["ObjectID"];
|
var id = sparams["ObjectID"];
|
||||||
var flag = sparams["BrowseFlag"];
|
var flag = sparams["BrowseFlag"];
|
||||||
@@ -255,95 +224,100 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
start = startVal;
|
start = startVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = new XmlWriterSettings
|
||||||
|
{
|
||||||
|
Encoding = Encoding.UTF8,
|
||||||
|
CloseOutput = false,
|
||||||
|
OmitXmlDeclaration = true,
|
||||||
|
ConformanceLevel = ConformanceLevel.Fragment
|
||||||
|
};
|
||||||
|
|
||||||
|
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
|
||||||
|
|
||||||
int totalCount;
|
int totalCount;
|
||||||
|
|
||||||
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
|
var dlnaOptions = _config.GetDlnaConfiguration();
|
||||||
|
|
||||||
|
using (var writer = XmlWriter.Create(builder, settings))
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings()
|
//writer.WriteStartDocument();
|
||||||
|
|
||||||
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
||||||
|
|
||||||
|
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
||||||
|
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
||||||
|
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
||||||
|
//didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
|
|
||||||
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
|
var serverItem = GetItemFromObjectId(id, user);
|
||||||
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
if (string.Equals(flag, "BrowseMetadata"))
|
||||||
{
|
{
|
||||||
Encoding = Encoding.UTF8,
|
totalCount = 1;
|
||||||
CloseOutput = false,
|
|
||||||
OmitXmlDeclaration = true,
|
|
||||||
ConformanceLevel = ConformanceLevel.Fragment
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var writer = XmlWriter.Create(builder, settings))
|
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
||||||
{
|
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(id, _user);
|
|
||||||
var item = serverItem.Item;
|
|
||||||
|
|
||||||
|
|
||||||
if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
totalCount = 1;
|
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
||||||
|
|
||||||
if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
|
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
||||||
{
|
|
||||||
var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
|
|
||||||
|
|
||||||
_didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, item, _user, null, null, deviceId, filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
provided++;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
|
_didlBuilder.WriteItemElement(dlnaOptions, writer, item, user, null, null, deviceId, filter);
|
||||||
totalCount = childrenResult.TotalRecordCount;
|
|
||||||
|
|
||||||
provided = childrenResult.Items.Count;
|
|
||||||
|
|
||||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
|
||||||
foreach (var i in childrenResult.Items)
|
|
||||||
{
|
|
||||||
var childItem = i.Item;
|
|
||||||
var displayStubType = i.StubType;
|
|
||||||
|
|
||||||
if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
|
|
||||||
{
|
|
||||||
var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
|
|
||||||
.TotalRecordCount;
|
|
||||||
|
|
||||||
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
provided++;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var childrenResult = (GetUserItems(item, serverItem.StubType, user, sortCriteria, start, requestedCount));
|
||||||
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
provided = childrenResult.Items.Length;
|
||||||
|
|
||||||
|
foreach (var i in childrenResult.Items)
|
||||||
|
{
|
||||||
|
var childItem = i.Item;
|
||||||
|
var displayStubType = i.StubType;
|
||||||
|
|
||||||
|
if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
|
||||||
|
{
|
||||||
|
var childCount = (GetUserItems(childItem, displayStubType, user, sortCriteria, null, 0))
|
||||||
|
.TotalRecordCount;
|
||||||
|
|
||||||
|
_didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, user, item, serverItem.StubType, deviceId, filter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.WriteFullEndElement();
|
||||||
|
//writer.WriteEndDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
|
var resXML = builder.ToString();
|
||||||
xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
|
|
||||||
xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
return new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("Result", resXML),
|
||||||
|
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
|
||||||
|
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
|
||||||
|
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleXBrowseByLetter(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
private IEnumerable<KeyValuePair<string, string>> HandleX_BrowseByLetter(IDictionary<string, string> sparams, User user, string deviceId)
|
||||||
{
|
{
|
||||||
// TODO: Implement this method
|
// TODO: Implement this method
|
||||||
HandleSearch(xmlWriter, sparams, deviceId);
|
return HandleSearch(sparams, user, deviceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleSearch(XmlWriter xmlWriter, IDictionary<string, string> sparams, string deviceId)
|
private IEnumerable<KeyValuePair<string, string>> HandleSearch(IDictionary<string, string> sparams, User user, string deviceId)
|
||||||
{
|
{
|
||||||
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
|
var searchCriteria = new SearchCriteria(GetValueOrDefault(sparams, "SearchCriteria", ""));
|
||||||
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
var sortCriteria = new SortCriteria(GetValueOrDefault(sparams, "SortCriteria", ""));
|
||||||
@@ -366,86 +340,99 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
start = startVal;
|
start = startVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
QueryResult<BaseItem> childrenResult;
|
var settings = new XmlWriterSettings
|
||||||
|
|
||||||
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
|
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings()
|
Encoding = Encoding.UTF8,
|
||||||
|
CloseOutput = false,
|
||||||
|
OmitXmlDeclaration = true,
|
||||||
|
ConformanceLevel = ConformanceLevel.Fragment
|
||||||
|
};
|
||||||
|
|
||||||
|
StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8);
|
||||||
|
int totalCount = 0;
|
||||||
|
int provided = 0;
|
||||||
|
|
||||||
|
using (var writer = XmlWriter.Create(builder, settings))
|
||||||
|
{
|
||||||
|
//writer.WriteStartDocument();
|
||||||
|
|
||||||
|
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
||||||
|
|
||||||
|
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
||||||
|
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
||||||
|
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
||||||
|
//didl.SetAttribute("xmlns:sec", NS_SEC);
|
||||||
|
|
||||||
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
|
var serverItem = GetItemFromObjectId(sparams["ContainerID"], user);
|
||||||
|
|
||||||
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
var childrenResult = (GetChildrenSorted(item, user, searchCriteria, sortCriteria, start, requestedCount));
|
||||||
|
|
||||||
|
totalCount = childrenResult.TotalRecordCount;
|
||||||
|
|
||||||
|
provided = childrenResult.Items.Length;
|
||||||
|
|
||||||
|
var dlnaOptions = _config.GetDlnaConfiguration();
|
||||||
|
|
||||||
|
foreach (var i in childrenResult.Items)
|
||||||
{
|
{
|
||||||
Encoding = Encoding.UTF8,
|
if (i.IsDisplayedAsFolder)
|
||||||
CloseOutput = false,
|
|
||||||
OmitXmlDeclaration = true,
|
|
||||||
ConformanceLevel = ConformanceLevel.Fragment
|
|
||||||
};
|
|
||||||
|
|
||||||
using (var writer = XmlWriter.Create(builder, settings))
|
|
||||||
{
|
|
||||||
writer.WriteStartElement(string.Empty, "DIDL-Lite", NS_DIDL);
|
|
||||||
|
|
||||||
writer.WriteAttributeString("xmlns", "dc", null, NS_DC);
|
|
||||||
writer.WriteAttributeString("xmlns", "dlna", null, NS_DLNA);
|
|
||||||
writer.WriteAttributeString("xmlns", "upnp", null, NS_UPNP);
|
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(sparams["ContainerID"], _user);
|
|
||||||
|
|
||||||
var item = serverItem.Item;
|
|
||||||
|
|
||||||
childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount);
|
|
||||||
|
|
||||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
|
||||||
|
|
||||||
foreach (var i in childrenResult.Items)
|
|
||||||
{
|
{
|
||||||
if (i.IsDisplayedAsFolder)
|
var childCount = (GetChildrenSorted(i, user, searchCriteria, sortCriteria, null, 0))
|
||||||
{
|
.TotalRecordCount;
|
||||||
var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0)
|
|
||||||
.TotalRecordCount;
|
|
||||||
|
|
||||||
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
|
_didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, i, _user, item, serverItem.StubType, deviceId, filter);
|
_didlBuilder.WriteItemElement(dlnaOptions, writer, i, user, item, serverItem.StubType, deviceId, filter);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlWriter.WriteElementString("Result", builder.ToString());
|
writer.WriteFullEndElement();
|
||||||
|
//writer.WriteEndDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
|
var resXML = builder.ToString();
|
||||||
xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
|
|
||||||
xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
|
return new List<KeyValuePair<string, string>>
|
||||||
|
{
|
||||||
|
new KeyValuePair<string,string>("Result", resXML),
|
||||||
|
new KeyValuePair<string,string>("NumberReturned", provided.ToString(_usCulture)),
|
||||||
|
new KeyValuePair<string,string>("TotalMatches", totalCount.ToString(_usCulture)),
|
||||||
|
new KeyValuePair<string,string>("UpdateID", _systemUpdateId.ToString(_usCulture))
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var folder = (Folder)item;
|
var folder = (Folder)item;
|
||||||
|
|
||||||
var sortOrders = folder.IsPreSorted
|
var sortOrders = new List<string>();
|
||||||
? Array.Empty<(string, SortOrder)>()
|
if (!folder.IsPreSorted)
|
||||||
: new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
{
|
||||||
|
sortOrders.Add(ItemSortBy.SortName);
|
||||||
|
}
|
||||||
|
|
||||||
string[] mediaTypes = Array.Empty<string>();
|
var mediaTypes = new List<string>();
|
||||||
bool? isFolder = null;
|
bool? isFolder = null;
|
||||||
|
|
||||||
if (search.SearchType == SearchType.Audio)
|
if (search.SearchType == SearchType.Audio)
|
||||||
{
|
{
|
||||||
mediaTypes = new[] { MediaType.Audio };
|
mediaTypes.Add(MediaType.Audio);
|
||||||
isFolder = false;
|
isFolder = false;
|
||||||
}
|
}
|
||||||
else if (search.SearchType == SearchType.Video)
|
else if (search.SearchType == SearchType.Video)
|
||||||
{
|
{
|
||||||
mediaTypes = new[] { MediaType.Video };
|
mediaTypes.Add(MediaType.Video);
|
||||||
isFolder = false;
|
isFolder = false;
|
||||||
}
|
}
|
||||||
else if (search.SearchType == SearchType.Image)
|
else if (search.SearchType == SearchType.Image)
|
||||||
{
|
{
|
||||||
mediaTypes = new[] { MediaType.Photo };
|
mediaTypes.Add(MediaType.Photo);
|
||||||
isFolder = false;
|
isFolder = false;
|
||||||
}
|
}
|
||||||
else if (search.SearchType == SearchType.Playlist)
|
else if (search.SearchType == SearchType.Playlist)
|
||||||
@@ -463,13 +450,13 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
{
|
{
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
OrderBy = sortOrders,
|
OrderBy = sortOrders.Select(i => new ValueTuple<string, SortOrder>(i, sort.SortOrder)).ToArray(),
|
||||||
User = user,
|
User = user,
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
IsMissing = false,
|
IsMissing = false,
|
||||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
|
||||||
IsFolder = isFolder,
|
IsFolder = isFolder,
|
||||||
MediaTypes = mediaTypes,
|
MediaTypes = mediaTypes.ToArray(),
|
||||||
DtoOptions = GetDtoOptions()
|
DtoOptions = GetDtoOptions()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -496,26 +483,27 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
|
return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((!stubType.HasValue || stubType.Value != StubType.Folder)
|
if (!stubType.HasValue || stubType.Value != StubType.Folder)
|
||||||
&& item is IHasCollectionType collectionFolder)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
var collectionFolder = item as IHasCollectionType;
|
||||||
|
if (collectionFolder != null && string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
|
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
if (collectionFolder != null && string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
|
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
if (collectionFolder != null && string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
|
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
|
if (collectionFolder != null && string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetFolders(item, user, stubType, sort, startIndex, limit);
|
return GetFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
if (collectionFolder != null && string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
|
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
@@ -536,7 +524,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
ExcludeItemTypes = new[] { typeof(Book).Name },
|
ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
|
||||||
IsPlaceHolder = false,
|
IsPlaceHolder = false,
|
||||||
DtoOptions = GetDtoOptions()
|
DtoOptions = GetDtoOptions()
|
||||||
};
|
};
|
||||||
@@ -677,7 +665,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list,
|
Items = list.ToArray(),
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -755,7 +743,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list,
|
Items = list.ToArray(),
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -770,11 +758,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return ApplyPaging(new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = folders,
|
Items = folders,
|
||||||
TotalRecordCount = folders.Length
|
TotalRecordCount = folders.Length
|
||||||
}, startIndex, limit);
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
||||||
@@ -860,7 +848,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list,
|
Items = list.ToArray(),
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = list.Count
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -871,10 +859,10 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
query.Parent = parent;
|
query.Parent = parent;
|
||||||
query.SetUser(user);
|
query.SetUser(user);
|
||||||
|
|
||||||
query.OrderBy = new[]
|
query.OrderBy = new ValueTuple<string, SortOrder>[]
|
||||||
{
|
{
|
||||||
(ItemSortBy.DatePlayed, SortOrder.Descending),
|
new ValueTuple<string, SortOrder> (ItemSortBy.DatePlayed, SortOrder.Descending),
|
||||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
new ValueTuple<string, SortOrder> (ItemSortBy.SortName, SortOrder.Ascending)
|
||||||
};
|
};
|
||||||
|
|
||||||
query.IsResumable = true;
|
query.IsResumable = true;
|
||||||
@@ -1120,7 +1108,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = new ValueTuple<string, SortOrder>[] { };
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
||||||
{
|
{
|
||||||
@@ -1137,7 +1125,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = new ValueTuple<string, SortOrder>[] { };
|
||||||
|
|
||||||
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
|
var result = _tvSeriesManager.GetNextUp(new NextUpQuery
|
||||||
{
|
{
|
||||||
@@ -1152,7 +1140,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private QueryResult<ServerItem> GetTvLatest(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetTvLatest(BaseItem parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = new ValueTuple<string, SortOrder>[] { };
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
||||||
{
|
{
|
||||||
@@ -1169,7 +1157,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private QueryResult<ServerItem> GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = new ValueTuple<string, SortOrder>[] { };
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
|
||||||
{
|
{
|
||||||
@@ -1273,14 +1261,13 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
|
|
||||||
private void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted)
|
private void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted)
|
||||||
{
|
{
|
||||||
if (isPreSorted)
|
var sortOrders = new List<string>();
|
||||||
|
if (!isPreSorted)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
sortOrders.Add(ItemSortBy.SortName);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
query.OrderBy = new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
query.OrderBy = sortOrders.Select(i => new ValueTuple<string, SortOrder>(i, sort.SortOrder)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> ApplyPaging(QueryResult<ServerItem> result, int? startIndex, int? limit)
|
private QueryResult<ServerItem> ApplyPaging(QueryResult<ServerItem> result, int? startIndex, int? limit)
|
||||||
@@ -1303,11 +1290,11 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
StubType? stubType = null;
|
StubType? stubType = null;
|
||||||
|
|
||||||
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
|
// After using PlayTo, MediaMonkey sends a request to the server trying to get item info
|
||||||
const string ParamsSrch = "Params=";
|
const string paramsSrch = "Params=";
|
||||||
var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
|
var paramsIndex = id.IndexOf(paramsSrch, StringComparison.OrdinalIgnoreCase);
|
||||||
if (paramsIndex != -1)
|
if (paramsIndex != -1)
|
||||||
{
|
{
|
||||||
id = id.Substring(paramsIndex + ParamsSrch.Length);
|
id = id.Substring(paramsIndex + paramsSrch.Length);
|
||||||
|
|
||||||
var parts = id.Split(';');
|
var parts = id.Split(';');
|
||||||
id = parts[23];
|
id = parts[23];
|
||||||
@@ -1335,7 +1322,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
_logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
||||||
|
|
||||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
#pragma warning disable CS1591
|
using System.Collections.Generic;
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class ControlRequest
|
public class ControlRequest
|
||||||
{
|
{
|
||||||
public IHeaderDictionary Headers { get; set; }
|
public IDictionary<string, string> Headers { get; set; }
|
||||||
|
|
||||||
public Stream InputXml { get; set; }
|
public Stream InputXml { get; set; }
|
||||||
|
|
||||||
@@ -18,7 +15,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
public ControlRequest()
|
public ControlRequest()
|
||||||
{
|
{
|
||||||
Headers = new HeaderDictionary();
|
Headers = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class ControlResponse
|
public class ControlResponse
|
||||||
{
|
{
|
||||||
public ControlResponse()
|
|
||||||
{
|
|
||||||
Headers = new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IDictionary<string, string> Headers { get; set; }
|
public IDictionary<string, string> Headers { get; set; }
|
||||||
|
|
||||||
public string Xml { get; set; }
|
public string Xml { get; set; }
|
||||||
|
|
||||||
public bool IsSuccessful { get; set; }
|
public bool IsSuccessful { get; set; }
|
||||||
|
|
||||||
|
public ControlResponse()
|
||||||
|
{
|
||||||
|
Headers = new Dictionary<string, string>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+155
-86
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -21,6 +18,7 @@ using MediaBrowser.Controller.Playlists;
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Extensions;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -45,30 +43,22 @@ namespace Emby.Dlna.Didl
|
|||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
public DidlBuilder(
|
public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, string accessToken, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, ILogger logger, ILibraryManager libraryManager, IMediaEncoder mediaEncoder)
|
||||||
DeviceProfile profile,
|
|
||||||
User user,
|
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
string serverAddress,
|
|
||||||
string accessToken,
|
|
||||||
IUserDataManager userDataManager,
|
|
||||||
ILocalizationManager localization,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
ILogger logger,
|
|
||||||
IMediaEncoder mediaEncoder)
|
|
||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
_user = user;
|
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_serverAddress = serverAddress;
|
_serverAddress = serverAddress;
|
||||||
_accessToken = accessToken;
|
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_accessToken = accessToken;
|
||||||
|
_user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeDlnaMediaUrl(string url)
|
public static string NormalizeDlnaMediaUrl(string url)
|
||||||
@@ -127,8 +117,7 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteItemElement(
|
public void WriteItemElement(DlnaOptions options,
|
||||||
DlnaOptions options,
|
|
||||||
XmlWriter writer,
|
XmlWriter writer,
|
||||||
BaseItem item,
|
BaseItem item,
|
||||||
User user,
|
User user,
|
||||||
@@ -160,7 +149,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
AddGeneralProperties(item, null, context, writer, filter);
|
AddGeneralProperties(item, null, context, writer, filter);
|
||||||
|
|
||||||
AddSamsungBookmarkInfo(item, user, writer, streamInfo);
|
AddSamsungBookmarkInfo(item, user, writer);
|
||||||
|
|
||||||
// refID?
|
// refID?
|
||||||
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
||||||
@@ -183,6 +172,19 @@ namespace Emby.Dlna.Didl
|
|||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetMimeType(string input)
|
||||||
|
{
|
||||||
|
var mime = MimeTypes.GetMimeType(input);
|
||||||
|
|
||||||
|
// TODO: Instead of being hard-coded here, this should probably be moved into all of the existing profiles
|
||||||
|
if (string.Equals(mime, "video/mp2t", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mime = "video/mpeg";
|
||||||
|
}
|
||||||
|
|
||||||
|
return mime;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
@@ -230,15 +232,12 @@ namespace Emby.Dlna.Didl
|
|||||||
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
|
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
|
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken)
|
||||||
|
.Where(subtitle => subtitle.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
foreach (var subtitle in subtitleProfiles)
|
foreach (var subtitle in subtitleProfiles)
|
||||||
{
|
{
|
||||||
if (subtitle.DeliveryMethod != SubtitleDeliveryMethod.External)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subtitleAdded = AddSubtitleElement(writer, subtitle);
|
var subtitleAdded = AddSubtitleElement(writer, subtitle);
|
||||||
|
|
||||||
if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
|
if (subtitleAdded && _profile.EnableSingleSubtitleLimit)
|
||||||
@@ -251,8 +250,7 @@ namespace Emby.Dlna.Didl
|
|||||||
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
|
private bool AddSubtitleElement(XmlWriter writer, SubtitleStreamInfo info)
|
||||||
{
|
{
|
||||||
var subtitleProfile = _profile.SubtitleProfiles
|
var subtitleProfile = _profile.SubtitleProfiles
|
||||||
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase)
|
.FirstOrDefault(i => string.Equals(info.Format, i.Format, StringComparison.OrdinalIgnoreCase) && i.Method == SubtitleDeliveryMethod.External);
|
||||||
&& i.Method == SubtitleDeliveryMethod.External);
|
|
||||||
|
|
||||||
if (subtitleProfile == null)
|
if (subtitleProfile == null)
|
||||||
{
|
{
|
||||||
@@ -267,7 +265,7 @@ namespace Emby.Dlna.Didl
|
|||||||
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
|
// <sec:CaptionInfo sec:type="srt">http://192.168.1.3:9999/video.srt</sec:CaptionInfo>
|
||||||
|
|
||||||
writer.WriteStartElement("sec", "CaptionInfoEx", null);
|
writer.WriteStartElement("sec", "CaptionInfoEx", null);
|
||||||
writer.WriteAttributeString("sec", "type", null, info.Format.ToLowerInvariant());
|
writer.WriteAttributeString("sec", "type", null, info.Format.ToLower());
|
||||||
|
|
||||||
writer.WriteString(info.Url);
|
writer.WriteString(info.Url);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
@@ -284,7 +282,7 @@ namespace Emby.Dlna.Didl
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||||
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
|
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLower());
|
||||||
writer.WriteAttributeString("protocolInfo", protocolInfo);
|
writer.WriteAttributeString("protocolInfo", protocolInfo);
|
||||||
|
|
||||||
writer.WriteString(info.Url);
|
writer.WriteString(info.Url);
|
||||||
@@ -373,7 +371,7 @@ namespace Emby.Dlna.Didl
|
|||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? MimeTypes.GetMimeType(filename)
|
? GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
@@ -389,39 +387,91 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
|
private string GetDisplayName(BaseItem item, StubType? itemStubType, BaseItem context)
|
||||||
{
|
{
|
||||||
if (itemStubType.HasValue)
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Latest)
|
||||||
{
|
{
|
||||||
switch (itemStubType.Value)
|
return _localization.GetLocalizedString("Latest");
|
||||||
{
|
}
|
||||||
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Playlists)
|
||||||
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
{
|
||||||
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
return _localization.GetLocalizedString("Playlists");
|
||||||
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
}
|
||||||
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
if (itemStubType.HasValue && itemStubType.Value == StubType.AlbumArtists)
|
||||||
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
{
|
||||||
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||||
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
}
|
||||||
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Albums)
|
||||||
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
{
|
||||||
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
|
return _localization.GetLocalizedString("Albums");
|
||||||
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
}
|
||||||
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Artists)
|
||||||
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
{
|
||||||
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
return _localization.GetLocalizedString("Artists");
|
||||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
}
|
||||||
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Songs)
|
||||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
{
|
||||||
default: break;
|
return _localization.GetLocalizedString("Songs");
|
||||||
}
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Genres)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("Genres");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteAlbums)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteArtists)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSongs)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.ContinueWatching)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderContinueWatching");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Movies)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("Movies");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Collections)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("Collections");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Favorites)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("Favorites");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.NextUp)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderNextUp");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteSeries)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.FavoriteEpisodes)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||||
|
}
|
||||||
|
if (itemStubType.HasValue && itemStubType.Value == StubType.Series)
|
||||||
|
{
|
||||||
|
return _localization.GetLocalizedString("Shows");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is Episode episode && context is Season season)
|
var episode = item as Episode;
|
||||||
|
var season = context as Season;
|
||||||
|
|
||||||
|
if (episode != null && season != null)
|
||||||
{
|
{
|
||||||
// This is a special embedded within a season
|
// This is a special embedded within a season
|
||||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
|
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0)
|
||||||
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
|
||||||
{
|
{
|
||||||
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
|
if (season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
||||||
|
{
|
||||||
|
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
if (item.IndexNumber.HasValue)
|
||||||
@@ -509,7 +559,7 @@ namespace Emby.Dlna.Didl
|
|||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?'));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? MimeTypes.GetMimeType(filename)
|
? GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
var contentFeatures = new ContentFeatureBuilder(_profile).BuildAudioHeader(streamInfo.Container,
|
||||||
@@ -534,10 +584,19 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsIdRoot(string id)
|
public static bool IsIdRoot(string id)
|
||||||
=> string.IsNullOrWhiteSpace(id)
|
{
|
||||||
|| string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
if (string.IsNullOrWhiteSpace(id) ||
|
||||||
|
|
||||||
|
string.Equals(id, "0", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|
||||||
// Samsung sometimes uses 1 as root
|
// Samsung sometimes uses 1 as root
|
||||||
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase);
|
|| string.Equals(id, "1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
public void WriteFolderElement(XmlWriter writer, BaseItem folder, StubType? stubType, BaseItem context, int childCount, Filter filter, string requestedId = null)
|
||||||
{
|
{
|
||||||
@@ -583,7 +642,7 @@ namespace Emby.Dlna.Didl
|
|||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer, StreamInfo streamInfo)
|
private void AddSamsungBookmarkInfo(BaseItem item, User user, XmlWriter writer)
|
||||||
{
|
{
|
||||||
if (!item.SupportsPositionTicksResume || item is Folder)
|
if (!item.SupportsPositionTicksResume || item is Folder)
|
||||||
{
|
{
|
||||||
@@ -607,11 +666,10 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var userdata = _userDataManager.GetUserData(user, item);
|
var userdata = _userDataManager.GetUserData(user, item);
|
||||||
var playbackPositionTicks = (streamInfo != null && streamInfo.StartPositionTicks > 0) ? streamInfo.StartPositionTicks : userdata.PlaybackPositionTicks;
|
|
||||||
|
|
||||||
if (playbackPositionTicks > 0)
|
if (userdata.PlaybackPositionTicks > 0)
|
||||||
{
|
{
|
||||||
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
|
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(userdata.PlaybackPositionTicks).TotalSeconds).ToString(_usCulture));
|
||||||
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +692,7 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
if (item.PremiereDate.HasValue)
|
if (item.PremiereDate.HasValue)
|
||||||
{
|
{
|
||||||
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o", CultureInfo.InvariantCulture), NS_DC);
|
AddValue(writer, "dc", "date", item.PremiereDate.Value.ToString("o"), NS_DC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,7 +808,7 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
|
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre.musicGenre");
|
||||||
}
|
}
|
||||||
else if (item is Genre)
|
else if (item is Genre || item is GameGenre)
|
||||||
{
|
{
|
||||||
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
|
writer.WriteString(_profile.RequiresPlainFolders ? "object.container.storageFolder" : "object.container.genre");
|
||||||
}
|
}
|
||||||
@@ -786,7 +844,7 @@ namespace Emby.Dlna.Didl
|
|||||||
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||||
// ?? PersonType.Actor;
|
// ?? PersonType.Actor;
|
||||||
|
|
||||||
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
// AddValue(writer, "upnp", type.ToLower(), actor.Name, NS_UPNP);
|
||||||
|
|
||||||
// index++;
|
// index++;
|
||||||
|
|
||||||
@@ -801,9 +859,10 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
AddCommonFields(item, itemStubType, context, writer, filter);
|
AddCommonFields(item, itemStubType, context, writer, filter);
|
||||||
|
|
||||||
|
var hasArtists = item as IHasArtist;
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
var hasAlbumArtists = item as IHasAlbumArtist;
|
||||||
|
|
||||||
if (item is IHasArtist hasArtists)
|
if (hasArtists != null)
|
||||||
{
|
{
|
||||||
foreach (var artist in hasArtists.Artists)
|
foreach (var artist in hasArtists.Artists)
|
||||||
{
|
{
|
||||||
@@ -873,7 +932,13 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
||||||
{
|
{
|
||||||
ImageDownloadInfo imageInfo = GetImageInfo(item);
|
ImageDownloadInfo imageInfo = null;
|
||||||
|
|
||||||
|
// Finally, just use the image from the item
|
||||||
|
if (imageInfo == null)
|
||||||
|
{
|
||||||
|
imageInfo = GetImageInfo(item);
|
||||||
|
}
|
||||||
|
|
||||||
if (imageInfo == null)
|
if (imageInfo == null)
|
||||||
{
|
{
|
||||||
@@ -903,6 +968,8 @@ namespace Emby.Dlna.Didl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
||||||
|
|
||||||
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
||||||
@@ -911,9 +978,6 @@ namespace Emby.Dlna.Didl
|
|||||||
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
|
AddImageResElement(item, writer, 4096, 4096, "png", "PNG_LRG");
|
||||||
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
|
AddImageResElement(item, writer, 160, 160, "png", "PNG_TN");
|
||||||
}
|
}
|
||||||
|
|
||||||
AddImageResElement(item, writer, 160, 160, "jpg", "JPEG_TN");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
||||||
@@ -954,7 +1018,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString("protocolInfo", string.Format(
|
||||||
"http-get:*:{0}:{1}",
|
"http-get:*:{0}:{1}",
|
||||||
MimeTypes.GetMimeType("file." + format),
|
GetMimeType("file." + format),
|
||||||
contentFeatures
|
contentFeatures
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -1030,8 +1094,8 @@ namespace Emby.Dlna.Didl
|
|||||||
//{
|
//{
|
||||||
// var size = _imageProcessor.GetImageSize(imageInfo);
|
// var size = _imageProcessor.GetImageSize(imageInfo);
|
||||||
|
|
||||||
// width = size.Width;
|
// width = Convert.ToInt32(size.Width);
|
||||||
// height = size.Height;
|
// height = Convert.ToInt32(size.Height);
|
||||||
//}
|
//}
|
||||||
//catch
|
//catch
|
||||||
//{
|
//{
|
||||||
@@ -1054,7 +1118,7 @@ namespace Emby.Dlna.Didl
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ImageDownloadInfo
|
class ImageDownloadInfo
|
||||||
{
|
{
|
||||||
internal Guid ItemId;
|
internal Guid ItemId;
|
||||||
internal string ImageTag;
|
internal string ImageTag;
|
||||||
@@ -1070,7 +1134,7 @@ namespace Emby.Dlna.Didl
|
|||||||
internal ItemImageInfo ItemImageInfo;
|
internal ItemImageInfo ItemImageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ImageUrlInfo
|
class ImageUrlInfo
|
||||||
{
|
{
|
||||||
internal string Url;
|
internal string Url;
|
||||||
|
|
||||||
@@ -1085,11 +1149,11 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
public static string GetClientId(Guid idValue, StubType? stubType)
|
public static string GetClientId(Guid idValue, StubType? stubType)
|
||||||
{
|
{
|
||||||
var id = idValue.ToString("N", CultureInfo.InvariantCulture);
|
var id = idValue.ToString("N");
|
||||||
|
|
||||||
if (stubType.HasValue)
|
if (stubType.HasValue)
|
||||||
{
|
{
|
||||||
id = stubType.Value.ToString().ToLowerInvariant() + "_" + id;
|
id = stubType.Value.ToString().ToLower() + "_" + id;
|
||||||
}
|
}
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
@@ -1099,12 +1163,13 @@ namespace Emby.Dlna.Didl
|
|||||||
{
|
{
|
||||||
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
||||||
_serverAddress,
|
_serverAddress,
|
||||||
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
info.ItemId.ToString("N"),
|
||||||
info.Type,
|
info.Type,
|
||||||
info.ImageTag,
|
info.ImageTag,
|
||||||
format,
|
format,
|
||||||
maxWidth.ToString(CultureInfo.InvariantCulture),
|
maxWidth.ToString(CultureInfo.InvariantCulture),
|
||||||
maxHeight.ToString(CultureInfo.InvariantCulture));
|
maxHeight.ToString(CultureInfo.InvariantCulture)
|
||||||
|
);
|
||||||
|
|
||||||
var width = info.Width;
|
var width = info.Width;
|
||||||
var height = info.Height;
|
var height = info.Height;
|
||||||
@@ -1113,11 +1178,15 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
if (width.HasValue && height.HasValue)
|
||||||
{
|
{
|
||||||
var newSize = DrawingUtils.Resize(
|
var newSize = DrawingUtils.Resize(new ImageSize
|
||||||
new ImageDimensions(width.Value, height.Value), 0, 0, maxWidth, maxHeight);
|
{
|
||||||
|
Height = height.Value,
|
||||||
|
Width = width.Value
|
||||||
|
|
||||||
width = newSize.Width;
|
}, 0, 0, maxWidth, maxHeight);
|
||||||
height = newSize.Height;
|
|
||||||
|
width = Convert.ToInt32(newSize.Width);
|
||||||
|
height = Convert.ToInt32(newSize.Height);
|
||||||
|
|
||||||
var normalizedFormat = format
|
var normalizedFormat = format
|
||||||
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
.Replace("jpeg", "jpg", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Model.Extensions;
|
using MediaBrowser.Model.Extensions;
|
||||||
|
|
||||||
@@ -19,7 +16,7 @@ namespace Emby.Dlna.Didl
|
|||||||
|
|
||||||
public Filter(string filter)
|
public Filter(string filter)
|
||||||
{
|
{
|
||||||
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
_all = StringHelper.EqualsIgnoreCase(filter, "*");
|
||||||
|
|
||||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|||||||
+28
-35
@@ -1,15 +1,9 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Emby.Dlna.Profiles;
|
using Emby.Dlna.Profiles;
|
||||||
using Emby.Dlna.Server;
|
using Emby.Dlna.Server;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
@@ -20,10 +14,9 @@ using MediaBrowser.Controller.Drawing;
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Reflection;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
@@ -35,7 +28,7 @@ namespace Emby.Dlna
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IServerApplicationHost _appHost;
|
private readonly IServerApplicationHost _appHost;
|
||||||
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
private readonly IAssemblyInfo _assemblyInfo;
|
||||||
|
|
||||||
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
||||||
|
|
||||||
@@ -44,8 +37,7 @@ namespace Emby.Dlna
|
|||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IAssemblyInfo assemblyInfo)
|
||||||
IServerApplicationHost appHost)
|
|
||||||
{
|
{
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
@@ -53,13 +45,14 @@ namespace Emby.Dlna
|
|||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger("Dlna");
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
|
_assemblyInfo = assemblyInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task InitProfilesAsync()
|
public void InitProfiles()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ExtractSystemProfilesAsync();
|
ExtractSystemProfiles();
|
||||||
LoadProfiles();
|
LoadProfiles();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -209,13 +202,16 @@ namespace Emby.Dlna
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
public DeviceProfile GetProfile(IDictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
if (headers == null)
|
if (headers == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(headers));
|
throw new ArgumentNullException(nameof(headers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert to case insensitive
|
||||||
|
headers = new Dictionary<string, string>(headers, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||||
|
|
||||||
if (profile != null)
|
if (profile != null)
|
||||||
@@ -231,12 +227,12 @@ namespace Emby.Dlna
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
|
private bool IsMatch(IDictionary<string, string> headers, DeviceIdentification profileInfo)
|
||||||
{
|
{
|
||||||
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
|
private bool IsMatch(IDictionary<string, string> headers, HttpHeaderInfo header)
|
||||||
{
|
{
|
||||||
// Handle invalid user setup
|
// Handle invalid user setup
|
||||||
if (string.IsNullOrEmpty(header.Name))
|
if (string.IsNullOrEmpty(header.Name))
|
||||||
@@ -244,14 +240,14 @@ namespace Emby.Dlna
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (headers.TryGetValue(header.Name, out StringValues value))
|
if (headers.TryGetValue(header.Name, out string value))
|
||||||
{
|
{
|
||||||
switch (header.Match)
|
switch (header.Match)
|
||||||
{
|
{
|
||||||
case HeaderMatchType.Equals:
|
case HeaderMatchType.Equals:
|
||||||
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
||||||
case HeaderMatchType.Substring:
|
case HeaderMatchType.Substring:
|
||||||
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
var isMatch = value.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;
|
return isMatch;
|
||||||
case HeaderMatchType.Regex:
|
case HeaderMatchType.Regex:
|
||||||
@@ -304,7 +300,7 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
profile = ReserializeProfile(tempProfile);
|
profile = ReserializeProfile(tempProfile);
|
||||||
|
|
||||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
profile.Id = path.ToLower().GetMD5().ToString("N");
|
||||||
|
|
||||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||||
|
|
||||||
@@ -356,48 +352,45 @@ namespace Emby.Dlna
|
|||||||
|
|
||||||
Info = new DeviceProfileInfo
|
Info = new DeviceProfileInfo
|
||||||
{
|
{
|
||||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
Id = file.FullName.ToLower().GetMD5().ToString("N"),
|
||||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||||
Type = type
|
Type = type
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ExtractSystemProfilesAsync()
|
private void ExtractSystemProfiles()
|
||||||
{
|
{
|
||||||
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
|
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
|
||||||
|
|
||||||
var systemProfilesPath = SystemProfilesPath;
|
var systemProfilesPath = SystemProfilesPath;
|
||||||
|
|
||||||
foreach (var name in _assembly.GetManifestResourceNames())
|
foreach (var name in _assemblyInfo.GetManifestResourceNames(GetType())
|
||||||
|
.Where(i => i.StartsWith(namespaceName))
|
||||||
|
.ToList())
|
||||||
{
|
{
|
||||||
if (!name.StartsWith(namespaceName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
||||||
|
|
||||||
var path = Path.Combine(systemProfilesPath, filename);
|
var path = Path.Combine(systemProfilesPath, filename);
|
||||||
|
|
||||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), name))
|
||||||
{
|
{
|
||||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||||
|
|
||||||
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(systemProfilesPath);
|
_fileSystem.CreateDirectory(systemProfilesPath);
|
||||||
|
|
||||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream);
|
stream.CopyTo(fileStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not necessary, but just to make it easy to find
|
// Not necessary, but just to make it easy to find
|
||||||
Directory.CreateDirectory(UserProfilesPath);
|
_fileSystem.CreateDirectory(UserProfilesPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeleteProfile(string id)
|
public void DeleteProfile(string id)
|
||||||
@@ -497,7 +490,7 @@ namespace Emby.Dlna
|
|||||||
internal string Path { get; set; }
|
internal string Path { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
public string GetServerDescriptionXml(IDictionary<string, string> headers, string serverUuId, string serverAddress)
|
||||||
{
|
{
|
||||||
var profile = GetProfile(headers) ??
|
var profile = GetProfile(headers) ??
|
||||||
GetDefaultProfile();
|
GetDefaultProfile();
|
||||||
@@ -513,12 +506,12 @@ namespace Emby.Dlna
|
|||||||
? ImageFormat.Png
|
? ImageFormat.Png
|
||||||
: ImageFormat.Jpg;
|
: ImageFormat.Jpg;
|
||||||
|
|
||||||
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
var resource = GetType().Namespace + ".Images." + filename.ToLower();
|
||||||
|
|
||||||
return new ImageStream
|
return new ImageStream
|
||||||
{
|
{
|
||||||
Format = format,
|
Format = format,
|
||||||
Stream = _assembly.GetManifestResourceStream(resource)
|
Stream = _assemblyInfo.GetManifestResourceStream(GetType(), resource)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
|
||||||
<TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release'" >true</TreatWarningsAsErrors>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
|
||||||
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -72,9 +58,4 @@
|
|||||||
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,21 +1,17 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public class EventSubscriptionResponse
|
public class EventSubscriptionResponse
|
||||||
{
|
{
|
||||||
|
public string Content { get; set; }
|
||||||
|
public string ContentType { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> Headers { get; set; }
|
||||||
|
|
||||||
public EventSubscriptionResponse()
|
public EventSubscriptionResponse()
|
||||||
{
|
{
|
||||||
Headers = new Dictionary<string, string>();
|
Headers = new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Content { get; set; }
|
|
||||||
|
|
||||||
public string ContentType { get; set; }
|
|
||||||
|
|
||||||
public Dictionary<string, string> Headers { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@@ -33,15 +29,25 @@ namespace Emby.Dlna.Eventing
|
|||||||
{
|
{
|
||||||
var subscription = GetSubscription(subscriptionId, false);
|
var subscription = GetSubscription(subscriptionId, false);
|
||||||
|
|
||||||
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
|
int timeoutSeconds;
|
||||||
int timeoutSeconds = subscription.TimeoutSeconds;
|
|
||||||
subscription.SubscriptionTime = DateTime.UtcNow;
|
|
||||||
|
|
||||||
_logger.LogDebug(
|
// Remove logging for now because some devices are sending this very frequently
|
||||||
"Renewing event subscription for {0} with timeout of {1} to {2}",
|
// TODO re-enable with dlna debug logging setting
|
||||||
subscription.NotificationType,
|
//_logger.LogDebug("Renewing event subscription for {0} with timeout of {1} to {2}",
|
||||||
timeoutSeconds,
|
// subscription.NotificationType,
|
||||||
subscription.CallbackUrl);
|
// timeout,
|
||||||
|
// subscription.CallbackUrl);
|
||||||
|
|
||||||
|
if (subscription != null)
|
||||||
|
{
|
||||||
|
subscription.TimeoutSeconds = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||||
|
timeoutSeconds = subscription.TimeoutSeconds;
|
||||||
|
subscription.SubscriptionTime = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timeoutSeconds = 300;
|
||||||
|
}
|
||||||
|
|
||||||
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
|
return GetEventSubscriptionResponse(subscriptionId, requestedTimeoutString, timeoutSeconds);
|
||||||
}
|
}
|
||||||
@@ -49,12 +55,14 @@ namespace Emby.Dlna.Eventing
|
|||||||
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
public EventSubscriptionResponse CreateEventSubscription(string notificationType, string requestedTimeoutString, string callbackUrl)
|
||||||
{
|
{
|
||||||
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
var timeout = ParseTimeout(requestedTimeoutString) ?? 300;
|
||||||
var id = "uuid:" + Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
var id = "uuid:" + Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
|
// Remove logging for now because some devices are sending this very frequently
|
||||||
notificationType,
|
// TODO re-enable with dlna debug logging setting
|
||||||
timeout,
|
//_logger.LogDebug("Creating event subscription for {0} with timeout of {1} to {2}",
|
||||||
callbackUrl);
|
// notificationType,
|
||||||
|
// timeout,
|
||||||
|
// callbackUrl);
|
||||||
|
|
||||||
_subscriptions.TryAdd(id, new EventSubscription
|
_subscriptions.TryAdd(id, new EventSubscription
|
||||||
{
|
{
|
||||||
@@ -168,7 +176,7 @@ namespace Emby.Dlna.Eventing
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (await _httpClient.SendAsync(options, new HttpMethod("NOTIFY")).ConfigureAwait(false))
|
using (await _httpClient.SendAsync(options, "NOTIFY").ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Emby.Dlna.Eventing
|
namespace Emby.Dlna.Eventing
|
||||||
@@ -16,8 +13,6 @@ namespace Emby.Dlna.Eventing
|
|||||||
|
|
||||||
public long TriggerCount { get; set; }
|
public long TriggerCount { get; set; }
|
||||||
|
|
||||||
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
|
||||||
|
|
||||||
public void IncrementTriggerCount()
|
public void IncrementTriggerCount()
|
||||||
{
|
{
|
||||||
if (TriggerCount == long.MaxValue)
|
if (TriggerCount == long.MaxValue)
|
||||||
@@ -27,5 +22,7 @@ namespace Emby.Dlna.Eventing
|
|||||||
|
|
||||||
TriggerCount++;
|
TriggerCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsExpired => SubscriptionTime.AddSeconds(TimeoutSeconds) >= DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
#pragma warning disable CS1591
|
using System.Collections.Generic;
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
@@ -10,14 +7,15 @@ namespace Emby.Dlna
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the content directory XML.
|
/// Gets the content directory XML.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="headers">The headers.</param>
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
string GetServiceXml();
|
string GetServiceXml(IDictionary<string, string> headers);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Processes the control request.
|
/// Processes the control request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>ControlResponse.</returns>
|
/// <returns>ControlResponse.</returns>
|
||||||
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
|
ControlResponse ProcessControlRequest(ControlRequest request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,9 +1,5 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Sockets;
|
using System.Linq;
|
||||||
using System.Globalization;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.PlayTo;
|
using Emby.Dlna.PlayTo;
|
||||||
@@ -24,10 +20,11 @@ using MediaBrowser.Model.Dlna;
|
|||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.System;
|
using MediaBrowser.Model.System;
|
||||||
|
using MediaBrowser.Model.Threading;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Rssdp;
|
using Rssdp;
|
||||||
using Rssdp.Infrastructure;
|
using Rssdp.Infrastructure;
|
||||||
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Main
|
namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
@@ -53,7 +50,9 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
private SsdpDevicePublisher _Publisher;
|
private SsdpDevicePublisher _Publisher;
|
||||||
|
|
||||||
|
private readonly ITimerFactory _timerFactory;
|
||||||
private readonly ISocketFactory _socketFactory;
|
private readonly ISocketFactory _socketFactory;
|
||||||
|
private readonly IEnvironmentInfo _environmentInfo;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
|
|
||||||
private ISsdpCommunicationsServer _communicationsServer;
|
private ISsdpCommunicationsServer _communicationsServer;
|
||||||
@@ -79,8 +78,11 @@ namespace Emby.Dlna.Main
|
|||||||
IDeviceDiscovery deviceDiscovery,
|
IDeviceDiscovery deviceDiscovery,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
ISocketFactory socketFactory,
|
ISocketFactory socketFactory,
|
||||||
|
ITimerFactory timerFactory,
|
||||||
|
IEnvironmentInfo environmentInfo,
|
||||||
INetworkManager networkManager,
|
INetworkManager networkManager,
|
||||||
IUserViewManager userViewManager,
|
IUserViewManager userViewManager,
|
||||||
|
IXmlReaderSettingsFactory xmlReaderSettingsFactory,
|
||||||
ITVSeriesManager tvSeriesManager)
|
ITVSeriesManager tvSeriesManager)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
@@ -97,11 +99,12 @@ namespace Emby.Dlna.Main
|
|||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_socketFactory = socketFactory;
|
_socketFactory = socketFactory;
|
||||||
|
_timerFactory = timerFactory;
|
||||||
|
_environmentInfo = environmentInfo;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_logger = loggerFactory.CreateLogger("Dlna");
|
_logger = loggerFactory.CreateLogger("Dlna");
|
||||||
|
|
||||||
ContentDirectory = new ContentDirectory.ContentDirectory(
|
ContentDirectory = new ContentDirectory.ContentDirectory(dlnaManager,
|
||||||
dlnaManager,
|
|
||||||
userDataManager,
|
userDataManager,
|
||||||
imageProcessor,
|
imageProcessor,
|
||||||
libraryManager,
|
libraryManager,
|
||||||
@@ -113,17 +116,18 @@ namespace Emby.Dlna.Main
|
|||||||
mediaSourceManager,
|
mediaSourceManager,
|
||||||
userViewManager,
|
userViewManager,
|
||||||
mediaEncoder,
|
mediaEncoder,
|
||||||
|
xmlReaderSettingsFactory,
|
||||||
tvSeriesManager);
|
tvSeriesManager);
|
||||||
|
|
||||||
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient);
|
ConnectionManager = new ConnectionManager.ConnectionManager(dlnaManager, config, _logger, httpClient, xmlReaderSettingsFactory);
|
||||||
|
|
||||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config);
|
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrar(_logger, httpClient, config, xmlReaderSettingsFactory);
|
||||||
Current = this;
|
Current = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunAsync()
|
public void Run()
|
||||||
{
|
{
|
||||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
((DlnaManager)_dlnaManager).InitProfiles();
|
||||||
|
|
||||||
ReloadComponents();
|
ReloadComponents();
|
||||||
|
|
||||||
@@ -169,10 +173,9 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
if (_communicationsServer == null)
|
if (_communicationsServer == null)
|
||||||
{
|
{
|
||||||
var enableMultiSocketBinding = OperatingSystem.Id == OperatingSystemId.Windows ||
|
var enableMultiSocketBinding = _environmentInfo.OperatingSystem == MediaBrowser.Model.System.OperatingSystem.Windows;
|
||||||
OperatingSystem.Id == OperatingSystemId.Linux;
|
|
||||||
|
|
||||||
_communicationsServer = new SsdpCommunicationsServer(_config, _socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
||||||
{
|
{
|
||||||
IsShared = true
|
IsShared = true
|
||||||
};
|
};
|
||||||
@@ -230,7 +233,7 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_Publisher = new SsdpDevicePublisher(_communicationsServer, _networkManager, OperatingSystem.Name, Environment.OSVersion.VersionString, _config.GetDlnaConfiguration().SendOnlyMatchedHost);
|
_Publisher = new SsdpDevicePublisher(_communicationsServer, _timerFactory, _environmentInfo.OperatingSystemName, _environmentInfo.OperatingSystemVersion);
|
||||||
_Publisher.LogFunction = LogMessage;
|
_Publisher.LogFunction = LogMessage;
|
||||||
_Publisher.SupportPnpRootDevice = false;
|
_Publisher.SupportPnpRootDevice = false;
|
||||||
|
|
||||||
@@ -246,21 +249,21 @@ namespace Emby.Dlna.Main
|
|||||||
|
|
||||||
private async Task RegisterServerEndpoints()
|
private async Task RegisterServerEndpoints()
|
||||||
{
|
{
|
||||||
var addresses = await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false);
|
var addresses = (await _appHost.GetLocalIpAddresses(CancellationToken.None).ConfigureAwait(false)).ToList();
|
||||||
|
|
||||||
var udn = CreateUuid(_appHost.SystemId);
|
var udn = CreateUuid(_appHost.SystemId);
|
||||||
|
|
||||||
foreach (var address in addresses)
|
foreach (var address in addresses)
|
||||||
{
|
{
|
||||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
// TODO: Remove this condition on platforms that support it
|
||||||
{
|
//if (address.AddressFamily == IpAddressFamily.InterNetworkV6)
|
||||||
// Not support IPv6 right now
|
//{
|
||||||
continue;
|
// continue;
|
||||||
}
|
//}
|
||||||
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address);
|
_logger.LogInformation("Registering publisher for {0} on {1}", fullService, address.ToString());
|
||||||
|
|
||||||
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
||||||
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
|
var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorUri);
|
||||||
@@ -269,8 +272,6 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
CacheLifetime = TimeSpan.FromSeconds(1800), //How long SSDP clients can cache this info.
|
||||||
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
Location = uri, // Must point to the URL that serves your devices UPnP description document.
|
||||||
Address = address,
|
|
||||||
SubnetMask = _networkManager.GetLocalIpSubnetMask(address),
|
|
||||||
FriendlyName = "Jellyfin",
|
FriendlyName = "Jellyfin",
|
||||||
Manufacturer = "Jellyfin",
|
Manufacturer = "Jellyfin",
|
||||||
ModelName = "Jellyfin Server",
|
ModelName = "Jellyfin Server",
|
||||||
@@ -311,7 +312,7 @@ namespace Emby.Dlna.Main
|
|||||||
{
|
{
|
||||||
guid = text.GetMD5();
|
guid = text.GetMD5();
|
||||||
}
|
}
|
||||||
return guid.ToString("N", CultureInfo.InvariantCulture);
|
return guid.ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
||||||
@@ -352,7 +353,8 @@ namespace Emby.Dlna.Main
|
|||||||
_userDataManager,
|
_userDataManager,
|
||||||
_localization,
|
_localization,
|
||||||
_mediaSourceManager,
|
_mediaSourceManager,
|
||||||
_mediaEncoder);
|
_mediaEncoder,
|
||||||
|
_timerFactory);
|
||||||
|
|
||||||
_manager.Start();
|
_manager.Start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,43 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Xml;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
public class ControlHandler : BaseControlHandler
|
public class ControlHandler : BaseControlHandler
|
||||||
{
|
{
|
||||||
public ControlHandler(IServerConfigurationManager config, ILogger logger)
|
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||||
: base(config, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void WriteResult(string methodName, IDictionary<string, string> methodParams, XmlWriter xmlWriter)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "IsAuthorized", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleIsAuthorized();
|
||||||
HandleIsAuthorized(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(methodName, "IsValidated", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
return HandleIsValidated();
|
||||||
HandleIsValidated(xmlWriter);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleIsAuthorized(XmlWriter xmlWriter)
|
private static IEnumerable<KeyValuePair<string, string>> HandleIsAuthorized()
|
||||||
=> xmlWriter.WriteElementString("Result", "1");
|
{
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "Result", "1" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static void HandleIsValidated(XmlWriter xmlWriter)
|
private static IEnumerable<KeyValuePair<string, string>> HandleIsValidated()
|
||||||
=> xmlWriter.WriteElementString("Result", "1");
|
{
|
||||||
|
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "Result", "1" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ControlHandler(IServerConfigurationManager config, ILogger logger, IXmlReaderSettingsFactory xmlReaderSettingsFactory) : base(config, logger, xmlReaderSettingsFactory)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#pragma warning disable CS1591
|
using System.Collections.Generic;
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Model.Xml;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.MediaReceiverRegistrar
|
namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
@@ -12,26 +10,26 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
|||||||
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
public class MediaReceiverRegistrar : BaseService, IMediaReceiverRegistrar
|
||||||
{
|
{
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
protected readonly IXmlReaderSettingsFactory XmlReaderSettingsFactory;
|
||||||
|
|
||||||
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
|
public MediaReceiverRegistrar(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IXmlReaderSettingsFactory xmlReaderSettingsFactory)
|
||||||
: base(logger, httpClient)
|
: base(logger, httpClient)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
|
XmlReaderSettingsFactory = xmlReaderSettingsFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public string GetServiceXml(IDictionary<string, string> headers)
|
||||||
public string GetServiceXml()
|
|
||||||
{
|
{
|
||||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||||
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
|
||||||
{
|
{
|
||||||
return new ControlHandler(
|
return new ControlHandler(
|
||||||
_config,
|
_config,
|
||||||
Logger)
|
Logger, XmlReaderSettingsFactory)
|
||||||
.ProcessControlRequestAsync(request);
|
.ProcessControlRequest(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.PlayTo
|
||||||
|
{
|
||||||
|
public class CurrentIdEventArgs : EventArgs
|
||||||
|
{
|
||||||
|
public string Id { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
+144
-206
@@ -1,19 +1,16 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
using Emby.Dlna.Server;
|
||||||
using Emby.Dlna.Ssdp;
|
using Emby.Dlna.Ssdp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Model.Threading;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
@@ -22,7 +19,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
#region Fields & Properties
|
#region Fields & Properties
|
||||||
|
|
||||||
private Timer _timer;
|
private ITimer _timer;
|
||||||
|
|
||||||
public DeviceInfo Properties { get; set; }
|
public DeviceInfo Properties { get; set; }
|
||||||
|
|
||||||
@@ -43,7 +40,12 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public TimeSpan? Duration { get; set; }
|
public TimeSpan? Duration { get; set; }
|
||||||
|
|
||||||
public TimeSpan Position { get; set; } = TimeSpan.FromSeconds(0);
|
private TimeSpan _position = TimeSpan.FromSeconds(0);
|
||||||
|
public TimeSpan Position
|
||||||
|
{
|
||||||
|
get => _position;
|
||||||
|
set => _position = value;
|
||||||
|
}
|
||||||
|
|
||||||
public TRANSPORTSTATE TransportState { get; private set; }
|
public TRANSPORTSTATE TransportState { get; private set; }
|
||||||
|
|
||||||
@@ -59,20 +61,24 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
|
public DateTime DateLastActivity { get; private set; }
|
||||||
public Action OnDeviceUnavailable { get; set; }
|
public Action OnDeviceUnavailable { get; set; }
|
||||||
|
|
||||||
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config)
|
private readonly ITimerFactory _timerFactory;
|
||||||
|
|
||||||
|
public Device(DeviceInfo deviceProperties, IHttpClient httpClient, ILogger logger, IServerConfigurationManager config, ITimerFactory timerFactory)
|
||||||
{
|
{
|
||||||
Properties = deviceProperties;
|
Properties = deviceProperties;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
_timerFactory = timerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Dlna Device.Start");
|
_logger.LogDebug("Dlna Device.Start");
|
||||||
_timer = new Timer(TimerCallback, null, 1000, Timeout.Infinite);
|
_timer = _timerFactory.Create(TimerCallback, null, 1000, Timeout.Infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DateTime _lastVolumeRefresh;
|
private DateTime _lastVolumeRefresh;
|
||||||
@@ -113,9 +119,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_volumeRefreshActive = true;
|
_volumeRefreshActive = true;
|
||||||
|
|
||||||
@@ -132,9 +136,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
lock (_timerLock)
|
lock (_timerLock)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
_volumeRefreshActive = false;
|
_volumeRefreshActive = false;
|
||||||
|
|
||||||
@@ -142,6 +144,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnPlaybackStartedExternally()
|
||||||
|
{
|
||||||
|
RestartTimer(true);
|
||||||
|
}
|
||||||
|
|
||||||
#region Commanding
|
#region Commanding
|
||||||
|
|
||||||
public Task VolumeDown(CancellationToken cancellationToken)
|
public Task VolumeDown(CancellationToken cancellationToken)
|
||||||
@@ -224,7 +231,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_logger.LogDebug("Setting mute");
|
_logger.LogDebug("Setting mute");
|
||||||
var value = mute ? 1 : 0;
|
var value = mute ? 1 : 0;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
IsMuted = mute;
|
IsMuted = mute;
|
||||||
@@ -254,7 +261,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
// Remote control will perform better
|
// Remote control will perform better
|
||||||
Volume = value;
|
Volume = value;
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +280,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format("{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
@@ -305,7 +312,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
await Task.Delay(50).ConfigureAwait(false);
|
await Task.Delay(50).ConfigureAwait(false);
|
||||||
@@ -326,9 +333,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private string CreateDidlMeta(string value)
|
private string CreateDidlMeta(string value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value))
|
||||||
{
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
|
||||||
|
|
||||||
return DescriptionXmlBuilder.Escape(value);
|
return DescriptionXmlBuilder.Escape(value);
|
||||||
}
|
}
|
||||||
@@ -337,17 +342,16 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Play");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
|
return new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPlay(CancellationToken cancellationToken)
|
public async Task SetPlay(CancellationToken cancellationToken)
|
||||||
@@ -365,13 +369,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Stop");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
RestartTimer(true);
|
RestartTimer(true);
|
||||||
@@ -383,13 +385,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "Pause");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1))
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
TransportState = TRANSPORTSTATE.PAUSED;
|
TransportState = TRANSPORTSTATE.PAUSED;
|
||||||
@@ -405,9 +405,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private async void TimerCallback(object sender)
|
private async void TimerCallback(object sender)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -427,6 +425,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DateLastActivity = DateTime.UtcNow;
|
||||||
|
|
||||||
if (transportState.HasValue)
|
if (transportState.HasValue)
|
||||||
{
|
{
|
||||||
// If we're not playing anything no need to get additional data
|
// If we're not playing anything no need to get additional data
|
||||||
@@ -505,9 +505,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetVolume");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetServiceRenderingControl();
|
var service = GetServiceRenderingControl();
|
||||||
|
|
||||||
@@ -516,21 +514,17 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
var volume = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetVolumeResponse").Select(i => i.Element("CurrentVolume")).FirstOrDefault(i => i != null);
|
||||||
var volumeValue = volume?.Value;
|
var volumeValue = volume == null ? null : volume.Value;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(volumeValue))
|
if (string.IsNullOrWhiteSpace(volumeValue))
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
Volume = int.Parse(volumeValue, UsCulture);
|
Volume = int.Parse(volumeValue, UsCulture);
|
||||||
|
|
||||||
@@ -551,9 +545,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
var command = rendererCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMute");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetServiceRenderingControl();
|
var service = GetServiceRenderingControl();
|
||||||
|
|
||||||
@@ -562,50 +554,45 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse")
|
var valueNode = result.Document.Descendants(uPnpNamespaces.RenderingControl + "GetMuteResponse").Select(i => i.Element("CurrentMute")).FirstOrDefault(i => i != null);
|
||||||
.Select(i => i.Element("CurrentMute"))
|
var value = valueNode == null ? null : valueNode.Value;
|
||||||
.FirstOrDefault(i => i != null);
|
|
||||||
|
|
||||||
IsMuted = string.Equals(valueNode?.Value, "1", StringComparison.OrdinalIgnoreCase);
|
IsMuted = string.Equals(value, "1", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<TRANSPORTSTATE?> GetTransportInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetTransportInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var transportState =
|
var transportState =
|
||||||
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
|
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
|
||||||
|
|
||||||
var transportStateValue = transportState == null ? null : transportState.Value;
|
var transportStateValue = transportState == null ? null : transportState.Value;
|
||||||
|
|
||||||
if (transportStateValue != null
|
if (transportStateValue != null)
|
||||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
|
||||||
{
|
{
|
||||||
return state;
|
if (Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
||||||
|
{
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -615,11 +602,10 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetMediaInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
@@ -627,13 +613,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
|
var track = result.Document.Descendants("CurrentURIMetaData").FirstOrDefault();
|
||||||
|
|
||||||
@@ -673,13 +657,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(bool, uBaseObject)> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
private async Task<Tuple<bool, uBaseObject>> GetPositionInfo(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
var command = avCommands.ServiceActions.FirstOrDefault(c => c.Name == "GetPositionInfo");
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
return new Tuple<bool, uBaseObject>(false, null);
|
||||||
return (false, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var service = GetAvTransportService();
|
var service = GetAvTransportService();
|
||||||
|
|
||||||
@@ -690,13 +672,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient, _config).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
return new Tuple<bool, uBaseObject>(false, null);
|
||||||
return (false, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
|
var trackUriElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackURI")).FirstOrDefault(i => i != null);
|
||||||
var trackUri = trackUriElem == null ? null : trackUriElem.Value;
|
var trackUri = trackUriElem == null ? null : trackUriElem.Value;
|
||||||
@@ -704,8 +684,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
var durationElem = result.Document.Descendants(uPnpNamespaces.AvTransport + "GetPositionInfoResponse").Select(i => i.Element("TrackDuration")).FirstOrDefault(i => i != null);
|
||||||
var duration = durationElem == null ? null : durationElem.Value;
|
var duration = durationElem == null ? null : durationElem.Value;
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(duration)
|
if (!string.IsNullOrWhiteSpace(duration) &&
|
||||||
&& !string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
!string.Equals(duration, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
Duration = TimeSpan.Parse(duration, UsCulture);
|
Duration = TimeSpan.Parse(duration, UsCulture);
|
||||||
}
|
}
|
||||||
@@ -727,75 +707,43 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (track == null)
|
if (track == null)
|
||||||
{
|
{
|
||||||
//If track is null, some vendors do this, use GetMediaInfo instead
|
//If track is null, some vendors do this, use GetMediaInfo instead
|
||||||
return (true, null);
|
return new Tuple<bool, uBaseObject>(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackString = (string)track;
|
var trackString = (string)track;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
if (string.IsNullOrWhiteSpace(trackString) || string.Equals(trackString, "NOT_IMPLEMENTED", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (true, null);
|
return new Tuple<bool, uBaseObject>(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
XElement uPnpResponse = null;
|
XElement uPnpResponse;
|
||||||
|
|
||||||
|
// Handle different variations sent back by devices
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
uPnpResponse = ParseResponse(trackString);
|
uPnpResponse = XElement.Parse(trackString);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Uncaught exception while parsing xml");
|
// first try to add a root node with a dlna namesapce
|
||||||
}
|
try
|
||||||
|
{
|
||||||
if (uPnpResponse == null)
|
uPnpResponse = XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + trackString + "</data>");
|
||||||
{
|
uPnpResponse = uPnpResponse.Descendants().First();
|
||||||
_logger.LogError("Failed to parse xml: \n {Xml}", trackString);
|
}
|
||||||
return (true, null);
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Unable to parse xml {0}", trackString);
|
||||||
|
return new Tuple<bool, uBaseObject>(true, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
var e = uPnpResponse.Element(uPnpNamespaces.items);
|
||||||
|
|
||||||
var uTrack = CreateUBaseObject(e, trackUri);
|
var uTrack = CreateUBaseObject(e, trackUri);
|
||||||
|
|
||||||
return (true, uTrack);
|
return new Tuple<bool, uBaseObject>(true, uTrack);
|
||||||
}
|
|
||||||
|
|
||||||
private XElement ParseResponse(string xml)
|
|
||||||
{
|
|
||||||
// Handle different variations sent back by devices
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return XElement.Parse(xml);
|
|
||||||
}
|
|
||||||
catch (XmlException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// first try to add a root node with a dlna namesapce
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return XElement.Parse("<data xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">" + xml + "</data>")
|
|
||||||
.Descendants()
|
|
||||||
.First();
|
|
||||||
}
|
|
||||||
catch (XmlException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// some devices send back invalid xml
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return XElement.Parse(xml.Replace("&", "&"));
|
|
||||||
}
|
|
||||||
catch (XmlException)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
private static uBaseObject CreateUBaseObject(XElement container, string trackUri)
|
||||||
@@ -853,9 +801,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
|
private async Task<TransportCommands> GetAVProtocolAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (AvCommands != null)
|
var avCommands = AvCommands;
|
||||||
|
|
||||||
|
if (avCommands != null)
|
||||||
{
|
{
|
||||||
return AvCommands;
|
return avCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
@@ -871,19 +821,22 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClient, _config);
|
||||||
|
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
AvCommands = TransportCommands.Create(document);
|
avCommands = TransportCommands.Create(document);
|
||||||
return AvCommands;
|
AvCommands = avCommands;
|
||||||
|
return avCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
|
private async Task<TransportCommands> GetRenderingProtocolAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (RendererCommands != null)
|
var rendererCommands = RendererCommands;
|
||||||
|
|
||||||
|
if (rendererCommands != null)
|
||||||
{
|
{
|
||||||
return RendererCommands;
|
return rendererCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
@@ -892,6 +845,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
var avService = GetServiceRenderingControl();
|
var avService = GetServiceRenderingControl();
|
||||||
|
|
||||||
if (avService == null)
|
if (avService == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Device AvService is null");
|
throw new ArgumentException("Device AvService is null");
|
||||||
@@ -899,12 +853,13 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
string url = NormalizeUrl(Properties.BaseUrl, avService.ScpdUrl);
|
||||||
|
|
||||||
var httpClient = new SsdpHttpClient(_httpClient);
|
var httpClient = new SsdpHttpClient(_httpClient, _config);
|
||||||
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
_logger.LogDebug("Dlna Device.GetRenderingProtocolAsync");
|
||||||
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
var document = await httpClient.GetDataAsync(url, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
RendererCommands = TransportCommands.Create(document);
|
rendererCommands = TransportCommands.Create(document);
|
||||||
return RendererCommands;
|
RendererCommands = rendererCommands;
|
||||||
|
return rendererCommands;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string NormalizeUrl(string baseUrl, string url)
|
private string NormalizeUrl(string baseUrl, string url)
|
||||||
@@ -916,103 +871,85 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!url.Contains("/"))
|
if (!url.Contains("/"))
|
||||||
{
|
|
||||||
url = "/dmr/" + url;
|
url = "/dmr/" + url;
|
||||||
}
|
|
||||||
|
|
||||||
if (!url.StartsWith("/"))
|
if (!url.StartsWith("/"))
|
||||||
{
|
|
||||||
url = "/" + url;
|
url = "/" + url;
|
||||||
}
|
|
||||||
|
|
||||||
return baseUrl + url;
|
return baseUrl + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TransportCommands AvCommands { get; set; }
|
private TransportCommands AvCommands
|
||||||
|
|
||||||
private TransportCommands RendererCommands { get; set; }
|
|
||||||
|
|
||||||
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
var ssdpHttpClient = new SsdpHttpClient(httpClient);
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransportCommands RendererCommands
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Device> CreateuPnpDeviceAsync(Uri url, IHttpClient httpClient, IServerConfigurationManager config, ILogger logger, ITimerFactory timerFactory, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var ssdpHttpClient = new SsdpHttpClient(httpClient, config);
|
||||||
|
|
||||||
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
var document = await ssdpHttpClient.GetDataAsync(url.ToString(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var deviceProperties = new DeviceInfo();
|
||||||
|
|
||||||
var friendlyNames = new List<string>();
|
var friendlyNames = new List<string>();
|
||||||
|
|
||||||
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
|
var name = document.Descendants(uPnpNamespaces.ud.GetName("friendlyName")).FirstOrDefault();
|
||||||
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
|
if (name != null && !string.IsNullOrWhiteSpace(name.Value))
|
||||||
{
|
|
||||||
friendlyNames.Add(name.Value);
|
friendlyNames.Add(name.Value);
|
||||||
}
|
|
||||||
|
|
||||||
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
|
var room = document.Descendants(uPnpNamespaces.ud.GetName("roomName")).FirstOrDefault();
|
||||||
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
|
if (room != null && !string.IsNullOrWhiteSpace(room.Value))
|
||||||
{
|
|
||||||
friendlyNames.Add(room.Value);
|
friendlyNames.Add(room.Value);
|
||||||
}
|
|
||||||
|
|
||||||
var deviceProperties = new DeviceInfo()
|
deviceProperties.Name = string.Join(" ", friendlyNames.ToArray());
|
||||||
{
|
|
||||||
Name = string.Join(" ", friendlyNames),
|
|
||||||
BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port)
|
|
||||||
};
|
|
||||||
|
|
||||||
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
|
var model = document.Descendants(uPnpNamespaces.ud.GetName("modelName")).FirstOrDefault();
|
||||||
if (model != null)
|
if (model != null)
|
||||||
{
|
|
||||||
deviceProperties.ModelName = model.Value;
|
deviceProperties.ModelName = model.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
|
var modelNumber = document.Descendants(uPnpNamespaces.ud.GetName("modelNumber")).FirstOrDefault();
|
||||||
if (modelNumber != null)
|
if (modelNumber != null)
|
||||||
{
|
|
||||||
deviceProperties.ModelNumber = modelNumber.Value;
|
deviceProperties.ModelNumber = modelNumber.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
|
var uuid = document.Descendants(uPnpNamespaces.ud.GetName("UDN")).FirstOrDefault();
|
||||||
if (uuid != null)
|
if (uuid != null)
|
||||||
{
|
|
||||||
deviceProperties.UUID = uuid.Value;
|
deviceProperties.UUID = uuid.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
|
var manufacturer = document.Descendants(uPnpNamespaces.ud.GetName("manufacturer")).FirstOrDefault();
|
||||||
if (manufacturer != null)
|
if (manufacturer != null)
|
||||||
{
|
|
||||||
deviceProperties.Manufacturer = manufacturer.Value;
|
deviceProperties.Manufacturer = manufacturer.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
|
var manufacturerUrl = document.Descendants(uPnpNamespaces.ud.GetName("manufacturerURL")).FirstOrDefault();
|
||||||
if (manufacturerUrl != null)
|
if (manufacturerUrl != null)
|
||||||
{
|
|
||||||
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
deviceProperties.ManufacturerUrl = manufacturerUrl.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
|
var presentationUrl = document.Descendants(uPnpNamespaces.ud.GetName("presentationURL")).FirstOrDefault();
|
||||||
if (presentationUrl != null)
|
if (presentationUrl != null)
|
||||||
{
|
|
||||||
deviceProperties.PresentationUrl = presentationUrl.Value;
|
deviceProperties.PresentationUrl = presentationUrl.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
|
var modelUrl = document.Descendants(uPnpNamespaces.ud.GetName("modelURL")).FirstOrDefault();
|
||||||
if (modelUrl != null)
|
if (modelUrl != null)
|
||||||
{
|
|
||||||
deviceProperties.ModelUrl = modelUrl.Value;
|
deviceProperties.ModelUrl = modelUrl.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
|
var serialNumber = document.Descendants(uPnpNamespaces.ud.GetName("serialNumber")).FirstOrDefault();
|
||||||
if (serialNumber != null)
|
if (serialNumber != null)
|
||||||
{
|
|
||||||
deviceProperties.SerialNumber = serialNumber.Value;
|
deviceProperties.SerialNumber = serialNumber.Value;
|
||||||
}
|
|
||||||
|
|
||||||
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
|
var modelDescription = document.Descendants(uPnpNamespaces.ud.GetName("modelDescription")).FirstOrDefault();
|
||||||
if (modelDescription != null)
|
if (modelDescription != null)
|
||||||
{
|
|
||||||
deviceProperties.ModelDescription = modelDescription.Value;
|
deviceProperties.ModelDescription = modelDescription.Value;
|
||||||
}
|
|
||||||
|
deviceProperties.BaseUrl = string.Format("http://{0}:{1}", url.Host, url.Port);
|
||||||
|
|
||||||
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
|
var icon = document.Descendants(uPnpNamespaces.ud.GetName("icon")).FirstOrDefault();
|
||||||
|
|
||||||
if (icon != null)
|
if (icon != null)
|
||||||
{
|
{
|
||||||
deviceProperties.Icon = CreateIcon(icon);
|
deviceProperties.Icon = CreateIcon(icon);
|
||||||
@@ -1021,15 +958,12 @@ namespace Emby.Dlna.PlayTo
|
|||||||
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
|
foreach (var services in document.Descendants(uPnpNamespaces.ud.GetName("serviceList")))
|
||||||
{
|
{
|
||||||
if (services == null)
|
if (services == null)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
|
var servicesList = services.Descendants(uPnpNamespaces.ud.GetName("service"));
|
||||||
|
|
||||||
if (servicesList == null)
|
if (servicesList == null)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var element in servicesList)
|
foreach (var element in servicesList)
|
||||||
{
|
{
|
||||||
@@ -1042,7 +976,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Device(deviceProperties, httpClient, logger, config);
|
var device = new Device(deviceProperties, httpClient, logger, config, timerFactory);
|
||||||
|
|
||||||
|
return device;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -1129,73 +1065,75 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
private void OnPlaybackStart(uBaseObject mediaInfo)
|
private void OnPlaybackStart(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
if (PlaybackStart != null)
|
||||||
{
|
{
|
||||||
return;
|
PlaybackStart.Invoke(this, new PlaybackStartEventArgs
|
||||||
|
{
|
||||||
|
MediaInfo = mediaInfo
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaybackStart?.Invoke(this, new PlaybackStartEventArgs
|
|
||||||
{
|
|
||||||
MediaInfo = mediaInfo
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
private void OnPlaybackProgress(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(mediaInfo.Url))
|
var mediaUrl = mediaInfo.Url;
|
||||||
|
if (string.IsNullOrWhiteSpace(mediaUrl))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaybackProgress?.Invoke(this, new PlaybackProgressEventArgs
|
if (PlaybackProgress != null)
|
||||||
{
|
{
|
||||||
MediaInfo = mediaInfo
|
PlaybackProgress.Invoke(this, new PlaybackProgressEventArgs
|
||||||
});
|
{
|
||||||
|
MediaInfo = mediaInfo
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStop(uBaseObject mediaInfo)
|
private void OnPlaybackStop(uBaseObject mediaInfo)
|
||||||
{
|
{
|
||||||
PlaybackStopped?.Invoke(this, new PlaybackStoppedEventArgs
|
if (PlaybackStopped != null)
|
||||||
{
|
{
|
||||||
MediaInfo = mediaInfo
|
PlaybackStopped.Invoke(this, new PlaybackStoppedEventArgs
|
||||||
});
|
{
|
||||||
|
MediaInfo = mediaInfo
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
|
private void OnMediaChanged(uBaseObject old, uBaseObject newMedia)
|
||||||
{
|
{
|
||||||
MediaChanged?.Invoke(this, new MediaChangedEventArgs
|
if (MediaChanged != null)
|
||||||
{
|
{
|
||||||
OldMediaInfo = old,
|
MediaChanged.Invoke(this, new MediaChangedEventArgs
|
||||||
NewMediaInfo = newMedia
|
{
|
||||||
});
|
OldMediaInfo = old,
|
||||||
|
NewMediaInfo = newMedia
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
||||||
bool _disposed;
|
bool _disposed;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
if (!_disposed)
|
||||||
GC.SuppressFinalize(this);
|
{
|
||||||
|
_disposed = true;
|
||||||
|
|
||||||
|
DisposeTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
private void DisposeTimer()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_timer != null)
|
||||||
{
|
{
|
||||||
return;
|
_timer.Dispose();
|
||||||
|
_timer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_timer?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
_timer = null;
|
|
||||||
Properties = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -20,8 +17,8 @@ using MediaBrowser.Model.Dto;
|
|||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
using MediaBrowser.Model.Services;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.WebUtilities;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
@@ -45,43 +42,30 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
private readonly string _serverAddress;
|
private readonly string _serverAddress;
|
||||||
private readonly string _accessToken;
|
private readonly string _accessToken;
|
||||||
|
private readonly DateTime _creationTime;
|
||||||
|
|
||||||
public bool IsSessionActive => !_disposed && _device != null;
|
public bool IsSessionActive => !_disposed && _device != null;
|
||||||
|
|
||||||
public bool SupportsMediaControl => IsSessionActive;
|
public bool SupportsMediaControl => IsSessionActive;
|
||||||
|
|
||||||
public PlayToController(
|
public PlayToController(SessionInfo session, ISessionManager sessionManager, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, string accessToken, IDeviceDiscovery deviceDiscovery, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IConfigurationManager config, IMediaEncoder mediaEncoder)
|
||||||
SessionInfo session,
|
|
||||||
ISessionManager sessionManager,
|
|
||||||
ILibraryManager libraryManager,
|
|
||||||
ILogger logger,
|
|
||||||
IDlnaManager dlnaManager,
|
|
||||||
IUserManager userManager,
|
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
string serverAddress,
|
|
||||||
string accessToken,
|
|
||||||
IDeviceDiscovery deviceDiscovery,
|
|
||||||
IUserDataManager userDataManager,
|
|
||||||
ILocalizationManager localization,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IConfigurationManager config,
|
|
||||||
IMediaEncoder mediaEncoder)
|
|
||||||
{
|
{
|
||||||
_session = session;
|
_session = session;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = logger;
|
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_serverAddress = serverAddress;
|
_serverAddress = serverAddress;
|
||||||
_accessToken = accessToken;
|
|
||||||
_deviceDiscovery = deviceDiscovery;
|
_deviceDiscovery = deviceDiscovery;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_config = config;
|
_config = config;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_accessToken = accessToken;
|
||||||
|
_logger = logger;
|
||||||
|
_creationTime = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Init(Device device)
|
public void Init(Device device)
|
||||||
@@ -104,10 +88,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
_sessionManager.ReportSessionEnded(_session.Id);
|
_sessionManager.ReportSessionEnded(_session.Id);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
// Could throw if the session is already gone
|
// Could throw if the session is already gone
|
||||||
_logger.LogError(ex, "Error reporting the end of session {Id}", _session.Id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,14 +98,20 @@ namespace Emby.Dlna.PlayTo
|
|||||||
{
|
{
|
||||||
var info = e.Argument;
|
var info = e.Argument;
|
||||||
|
|
||||||
if (!_disposed
|
info.Headers.TryGetValue("NTS", out string nts);
|
||||||
&& info.Headers.TryGetValue("USN", out string usn)
|
|
||||||
&& usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1
|
if (!info.Headers.TryGetValue("USN", out string usn)) usn = string.Empty;
|
||||||
&& (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1
|
|
||||||
|| (info.Headers.TryGetValue("NT", out string nt)
|
if (!info.Headers.TryGetValue("NT", out string nt)) nt = string.Empty;
|
||||||
&& nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)))
|
|
||||||
|
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
|
||||||
|
!_disposed)
|
||||||
{
|
{
|
||||||
OnDeviceUnavailable();
|
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
nt.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
OnDeviceUnavailable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +374,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
|
return _device.IsPaused ? _device.SetPlay(CancellationToken.None) : _device.SetPause(CancellationToken.None);
|
||||||
|
|
||||||
case PlaystateCommand.Seek:
|
case PlaystateCommand.Seek:
|
||||||
return Seek(command.SeekPositionTicks ?? 0);
|
{
|
||||||
|
return Seek(command.SeekPositionTicks ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
case PlaystateCommand.NextTrack:
|
case PlaystateCommand.NextTrack:
|
||||||
return SetPlaylistIndex(_currentPlaylistIndex + 1);
|
return SetPlaylistIndex(_currentPlaylistIndex + 1);
|
||||||
@@ -451,7 +442,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
|
var profile = _dlnaManager.GetProfile(deviceInfo.ToDeviceIdentification()) ??
|
||||||
_dlnaManager.GetDefaultProfile();
|
_dlnaManager.GetDefaultProfile();
|
||||||
|
|
||||||
var mediaSources = item is IHasMediaSources
|
var hasMediaSources = item as IHasMediaSources;
|
||||||
|
var mediaSources = hasMediaSources != null
|
||||||
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
|
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
|
||||||
: new List<MediaSourceInfo>();
|
: new List<MediaSourceInfo>();
|
||||||
|
|
||||||
@@ -460,7 +452,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
|
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
|
||||||
|
|
||||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
|
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _libraryManager, _mediaEncoder)
|
||||||
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||||
|
|
||||||
playlistItem.Didl = itemXml;
|
playlistItem.Didl = itemXml;
|
||||||
@@ -609,34 +601,22 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
if (!_disposed)
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
{
|
||||||
return;
|
_disposed = true;
|
||||||
}
|
|
||||||
|
_device.PlaybackStart -= _device_PlaybackStart;
|
||||||
|
_device.PlaybackProgress -= _device_PlaybackProgress;
|
||||||
|
_device.PlaybackStopped -= _device_PlaybackStopped;
|
||||||
|
_device.MediaChanged -= _device_MediaChanged;
|
||||||
|
//_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
||||||
|
_device.OnDeviceUnavailable = null;
|
||||||
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.PlaybackStart -= _device_PlaybackStart;
|
|
||||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
|
||||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
|
||||||
_device.OnDeviceUnavailable = null;
|
|
||||||
_device = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||||
|
|
||||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@@ -857,13 +837,13 @@ namespace Emby.Dlna.PlayTo
|
|||||||
if (index == -1) return request;
|
if (index == -1) return request;
|
||||||
|
|
||||||
var query = url.Substring(index + 1);
|
var query = url.Substring(index + 1);
|
||||||
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
QueryParamCollection values = MyHttpUtility.ParseQueryString(query);
|
||||||
|
|
||||||
request.DeviceProfileId = values.GetValueOrDefault("DeviceProfileId");
|
request.DeviceProfileId = values.Get("DeviceProfileId");
|
||||||
request.DeviceId = values.GetValueOrDefault("DeviceId");
|
request.DeviceId = values.Get("DeviceId");
|
||||||
request.MediaSourceId = values.GetValueOrDefault("MediaSourceId");
|
request.MediaSourceId = values.Get("MediaSourceId");
|
||||||
request.LiveStreamId = values.GetValueOrDefault("LiveStreamId");
|
request.LiveStreamId = values.Get("LiveStreamId");
|
||||||
request.IsDirectStream = string.Equals("true", values.GetValueOrDefault("Static"), StringComparison.OrdinalIgnoreCase);
|
request.IsDirectStream = string.Equals("true", values.Get("Static"), StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
request.AudioStreamIndex = GetIntValue(values, "AudioStreamIndex");
|
||||||
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
request.SubtitleStreamIndex = GetIntValue(values, "SubtitleStreamIndex");
|
||||||
@@ -877,9 +857,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int? GetIntValue(IReadOnlyDictionary<string, string> values, string name)
|
private static int? GetIntValue(QueryParamCollection values, string name)
|
||||||
{
|
{
|
||||||
var value = values.GetValueOrDefault(name);
|
var value = values.Get(name);
|
||||||
|
|
||||||
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
if (int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
{
|
{
|
||||||
@@ -889,9 +869,9 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long GetLongValue(IReadOnlyDictionary<string, string> values, string name)
|
private static long GetLongValue(QueryParamCollection values, string name)
|
||||||
{
|
{
|
||||||
var value = values.GetValueOrDefault(name);
|
var value = values.Get(name);
|
||||||
|
|
||||||
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
if (long.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
@@ -19,12 +14,14 @@ using MediaBrowser.Controller.Session;
|
|||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
|
using MediaBrowser.Model.Threading;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlayToManager : IDisposable
|
class PlayToManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
@@ -42,12 +39,13 @@ namespace Emby.Dlna.PlayTo
|
|||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly ITimerFactory _timerFactory;
|
||||||
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ITimerFactory timerFactory)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
@@ -63,14 +61,15 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_localization = localization;
|
_localization = localization;
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_timerFactory = timerFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
@@ -93,6 +92,11 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var cancellationToken = _disposeCancellationTokenSource.Token;
|
var cancellationToken = _disposeCancellationTokenSource.Token;
|
||||||
|
|
||||||
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _sessionLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@@ -104,11 +108,6 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sessionManager.Sessions.Any(i => usn.IndexOf(i.DeviceId, StringComparison.OrdinalIgnoreCase) != -1))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
|
await AddDevice(info, location, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -145,7 +144,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return usn;
|
return usn;
|
||||||
}
|
}
|
||||||
|
|
||||||
return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
return usn.GetMD5().ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
|
||||||
@@ -160,23 +159,25 @@ namespace Emby.Dlna.PlayTo
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
uuid = location.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
uuid = location.GetMD5().ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersionString, uuid, null, uri.OriginalString, null);
|
string deviceName = null;
|
||||||
|
|
||||||
|
var sessionInfo = _sessionManager.LogSessionActivity("DLNA", _appHost.ApplicationVersion, uuid, deviceName, uri.OriginalString, null);
|
||||||
|
|
||||||
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
|
var controller = sessionInfo.SessionControllers.OfType<PlayToController>().FirstOrDefault();
|
||||||
|
|
||||||
if (controller == null)
|
if (controller == null)
|
||||||
{
|
{
|
||||||
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, cancellationToken).ConfigureAwait(false);
|
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger, _timerFactory, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
string deviceName = device.Properties.Name;
|
deviceName = device.Properties.Name;
|
||||||
|
|
||||||
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
_sessionManager.UpdateDeviceName(sessionInfo.Id, deviceName);
|
||||||
|
|
||||||
string serverAddress;
|
string serverAddress;
|
||||||
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IPAddress.Any) || info.LocalIpAddress.Equals(IPAddress.IPv6Any))
|
if (info.LocalIpAddress == null || info.LocalIpAddress.Equals(IpAddressInfo.Any) || info.LocalIpAddress.Equals(IpAddressInfo.IPv6Any))
|
||||||
{
|
{
|
||||||
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
serverAddress = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -185,6 +186,8 @@ namespace Emby.Dlna.PlayTo
|
|||||||
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
|
serverAddress = _appHost.GetLocalApiUrl(info.LocalIpAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string accessToken = null;
|
||||||
|
|
||||||
controller = new PlayToController(sessionInfo,
|
controller = new PlayToController(sessionInfo,
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
_libraryManager,
|
_libraryManager,
|
||||||
@@ -193,7 +196,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
_userManager,
|
_userManager,
|
||||||
_imageProcessor,
|
_imageProcessor,
|
||||||
serverAddress,
|
serverAddress,
|
||||||
null,
|
accessToken,
|
||||||
_deviceDiscovery,
|
_deviceDiscovery,
|
||||||
_userDataManager,
|
_userDataManager,
|
||||||
_localization,
|
_localization,
|
||||||
@@ -234,7 +237,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user