mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-12-11 11:33:02 +03:00
Compare commits
14 Commits
renovate/m
...
v10.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf54a0e8be | ||
|
|
e73cf46e14 | ||
|
|
39c3b2f044 | ||
|
|
7a592a0f15 | ||
|
|
1fad64cd59 | ||
|
|
86e5dc4607 | ||
|
|
e98e4766f7 | ||
|
|
6e59671cf6 | ||
|
|
86a50367b2 | ||
|
|
0212c0b85f | ||
|
|
89d365122c | ||
|
|
9c0a8350d6 | ||
|
|
818d21718c | ||
|
|
0b551c0cd4 |
371
.ci/azure-pipelines.yml
Normal file
371
.ci/azure-pipelines.yml
Normal file
@@ -0,0 +1,371 @@
|
||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||
|
||||
variables:
|
||||
- name: TestProjects
|
||||
value: 'tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj'
|
||||
- name: RestoreBuildProjects
|
||||
value: 'Jellyfin.Server/Jellyfin.Server.csproj'
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
|
||||
jobs:
|
||||
- job: main_build
|
||||
displayName: Main Build
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
release:
|
||||
BuildConfiguration: Release
|
||||
debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Check out web"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Check out web (PR)"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: 'Install Node.js'
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web UI"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: yarn install
|
||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy the web UI
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||
contents: '**'
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: false # Optional
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Publish
|
||||
inputs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: '$(RestoreBuildProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Naming'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
|
||||
artifactName: 'Jellyfin.Naming'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Controller'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
||||
artifactName: 'Jellyfin.Controller'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Model'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
||||
artifactName: 'Jellyfin.Model'
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Artifact Common'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
||||
artifactName: 'Jellyfin.Common'
|
||||
|
||||
- job: main_test
|
||||
displayName: Main Test
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: false
|
||||
|
||||
- task: DotNetCoreCLI@2
|
||||
displayName: Build
|
||||
inputs:
|
||||
command: build
|
||||
publishWebProjects: false
|
||||
projects: '$(TestProjects)'
|
||||
arguments: '--configuration $(BuildConfiguration)'
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: VisualStudioTestPlatformInstaller@1
|
||||
inputs:
|
||||
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
|
||||
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
|
||||
|
||||
- task: VSTest@2
|
||||
inputs:
|
||||
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
|
||||
testAssemblyVer2: | # Required when testSelector == TestAssemblies
|
||||
**\bin\$(BuildConfiguration)\**\*test*.dll
|
||||
!**\obj\**
|
||||
!**\xunit.runner.visualstudio.testadapter.dll
|
||||
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
|
||||
#testPlan: # Required when testSelector == TestPlan
|
||||
#testSuite: # Required when testSelector == TestPlan
|
||||
#testConfiguration: # Required when testSelector == TestPlan
|
||||
#tcmTestRun: '$(test.RunId)' # Optional
|
||||
searchFolder: '$(System.DefaultWorkingDirectory)'
|
||||
#testFiltercriteria: # Optional
|
||||
#runOnlyImpactedTests: False # Optional
|
||||
#runAllTestsAfterXBuilds: '50' # Optional
|
||||
#uiTests: false # Optional
|
||||
#vstestLocationMethod: 'version' # Optional. Options: version, location
|
||||
#vsTestVersion: 'latest' # Optional. Options: latest, 16.0, 15.0, 14.0, toolsInstaller
|
||||
#vstestLocation: # Optional
|
||||
#runSettingsFile: # Optional
|
||||
#overrideTestrunParameters: # Optional
|
||||
#pathtoCustomTestAdapters: # Optional
|
||||
runInParallel: True # Optional
|
||||
runTestsInIsolation: True # Optional
|
||||
codeCoverageEnabled: True # Optional
|
||||
#otherConsoleOptions: # Optional
|
||||
#distributionBatchType: 'basedOnTestCases' # Optional. Options: basedOnTestCases, basedOnExecutionTime, basedOnAssembly
|
||||
#batchingBasedOnAgentsOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customBatchSize
|
||||
#customBatchSizeValue: '10' # Required when distributionBatchType == BasedOnTestCases && BatchingBasedOnAgentsOption == CustomBatchSize
|
||||
#batchingBasedOnExecutionTimeOption: 'autoBatchSize' # Optional. Options: autoBatchSize, customTimeBatchSize
|
||||
#customRunTimePerBatchValue: '60' # Required when distributionBatchType == BasedOnExecutionTime && BatchingBasedOnExecutionTimeOption == CustomTimeBatchSize
|
||||
#dontDistribute: False # Optional
|
||||
#testRunTitle: # Optional
|
||||
#platform: # Optional
|
||||
configuration: 'Debug' # Optional
|
||||
publishRunAttachments: true # Optional
|
||||
#diagnosticsEnabled: false # Optional
|
||||
#collectDumpOn: 'onAbortOnly' # Optional. Options: onAbortOnly, always, never
|
||||
#rerunFailedTests: False # Optional
|
||||
#rerunType: 'basedOnTestFailurePercentage' # Optional. Options: basedOnTestFailurePercentage, basedOnTestFailureCount
|
||||
#rerunFailedThreshold: '30' # Optional
|
||||
#rerunFailedTestCasesMaxLimit: '5' # Optional
|
||||
#rerunMaxAttempts: '3' # Optional
|
||||
|
||||
# - task: PublishTestResults@2
|
||||
# inputs:
|
||||
# testResultsFormat: 'VSTest' # Options: JUnit, NUnit, VSTest, xUnit, cTest
|
||||
# testResultsFiles: '**/*.trx'
|
||||
# #searchFolder: '$(System.DefaultWorkingDirectory)' # Optional
|
||||
# mergeTestResults: true # Optional
|
||||
# #failTaskOnFailedTests: false # Optional
|
||||
# #testRunTitle: # Optional
|
||||
# #buildPlatform: # Optional
|
||||
# #buildConfiguration: # Optional
|
||||
# #publishRunAttachments: true # Optional
|
||||
|
||||
- job: main_build_win
|
||||
displayName: Main Build Windows
|
||||
pool:
|
||||
vmImage: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
release:
|
||||
BuildConfiguration: Release
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Check out web"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Check out web (PR)"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: 'Install Node.js'
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: '10.x'
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web UI"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: yarn install
|
||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy the web UI
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
||||
contents: '**'
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: false # Optional
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: Clone the UX repository
|
||||
inputs:
|
||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: Build the NSIS Installer
|
||||
inputs:
|
||||
targetType: 'filePath' # Optional. Options: filePath, inline
|
||||
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
|
||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
||||
#script: '# Write your PowerShell commands here.Write-Host Hello World' # Required when targetType == Inline
|
||||
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
||||
#failOnStderr: false # Optional
|
||||
#ignoreLASTEXITCODE: false # Optional
|
||||
#pwsh: false # Optional
|
||||
workingDirectory: $(Build.SourcesDirectory) # Optional
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy the NSIS Installer to the artifact directory
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
|
||||
contents: 'jellyfin*.exe'
|
||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: 'Publish Setup Artifact'
|
||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
||||
inputs:
|
||||
targetPath: '$(build.artifactstagingdirectory)/setup'
|
||||
artifactName: 'Jellyfin Server Setup'
|
||||
|
||||
- job: dotnet_compat
|
||||
displayName: Compatibility Check
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
dependsOn: main_build
|
||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber']) # Only execute if the pullrequest numer is defined. (So not for normal CI builds)
|
||||
strategy:
|
||||
matrix:
|
||||
Naming:
|
||||
NugetPackageName: Jellyfin.Naming
|
||||
AssemblyFileName: Emby.Naming.dll
|
||||
Controller:
|
||||
NugetPackageName: Jellyfin.Controller
|
||||
AssemblyFileName: MediaBrowser.Controller.dll
|
||||
Model:
|
||||
NugetPackageName: Jellyfin.Model
|
||||
AssemblyFileName: MediaBrowser.Model.dll
|
||||
Common:
|
||||
NugetPackageName: Jellyfin.Common
|
||||
AssemblyFileName: MediaBrowser.Common.dll
|
||||
maxParallel: 2
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the New Assembly Build Artifact
|
||||
inputs:
|
||||
source: 'current' # Options: current, specific
|
||||
#preferTriggeringPipeline: false # Optional
|
||||
#tags: # Optional
|
||||
artifact: '$(NugetPackageName)' # Optional
|
||||
#patterns: '**' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/new-artifacts'
|
||||
#project: # Required when source == Specific
|
||||
#pipeline: # Required when source == Specific
|
||||
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
#runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
||||
#runId: # Required when source == Specific && runVersion == Specific
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy New Assembly to new-release folder
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
|
||||
contents: '**/*.dll'
|
||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the Reference Assembly Build Artifact
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
#preferTriggeringPipeline: false # Optional
|
||||
#tags: # Optional
|
||||
artifact: '$(NugetPackageName)' # Optional
|
||||
#patterns: '**' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/current-artifacts'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipeline: '$(System.DefinitionId)' # Required when source == Specific
|
||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
|
||||
#runId: # Required when source == Specific && runVersion == Specific
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: Copy Reference Assembly to current-release folder
|
||||
inputs:
|
||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
|
||||
contents: '**/*.dll'
|
||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||
cleanTargetFolder: true # Optional
|
||||
overWrite: true # Optional
|
||||
flattenFolders: true # Optional
|
||||
|
||||
- task: DownloadGitHubRelease@0
|
||||
displayName: Download ABI compatibility check tool from GitHub
|
||||
inputs:
|
||||
connection: Jellyfin Release Download
|
||||
userRepository: EraYaN/dotnet-compatibility
|
||||
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
|
||||
#version: # Required when defaultVersionType != Latest
|
||||
itemPattern: '**-ci.zip' # Optional
|
||||
downloadPath: '$(System.ArtifactsDirectory)'
|
||||
|
||||
- task: ExtractFiles@1
|
||||
displayName: Extract ABI compatibility check tool
|
||||
inputs:
|
||||
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
|
||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||
cleanDestinationFolder: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: Execute ABI compatibility check tool
|
||||
inputs:
|
||||
script: 'dotnet tools/CompatibilityCheckerCoreCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines'
|
||||
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
||||
#failOnStderr: false # Optional
|
||||
|
||||
|
||||
46
.ci/publish-nightly.yml
Normal file
46
.ci/publish-nightly.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
name: Nightly-$(date:yyyyMMdd).$(rev:r)
|
||||
|
||||
variables:
|
||||
- name: Version
|
||||
value: '1.0.0'
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- job: publish_artifacts_nightly
|
||||
displayName: Publish Artifacts Nightly
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the Windows Setup Artifact
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
artifact: 'Jellyfin Server Setup' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipelineId: 1 # Required when source == Specific
|
||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Create Drop directory'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
|
||||
|
||||
- task: CopyFilesOverSSH@0
|
||||
displayName: 'Copy the Windows Setup to the Repo'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||
contents: 'jellyfin_*.exe'
|
||||
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Clean up SCP symlink'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'
|
||||
48
.ci/publish-release.yml
Normal file
48
.ci/publish-release.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
|
||||
|
||||
variables:
|
||||
- name: Version
|
||||
value: '1.0.0'
|
||||
- name: UsedRunId
|
||||
value: 0
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
jobs:
|
||||
- job: publish_artifacts_release
|
||||
displayName: Publish Artifacts Release
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download the Windows Setup Artifact
|
||||
inputs:
|
||||
source: 'specific' # Options: current, specific
|
||||
artifact: 'Jellyfin Server Setup' # Optional
|
||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
||||
pipelineId: 1 # Required when source == Specific
|
||||
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
||||
runId: $(UsedRunId)
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Create Drop directory'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
|
||||
|
||||
- task: CopyFilesOverSSH@0
|
||||
displayName: 'Copy the Windows Setup to the Repo'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
||||
contents: 'jellyfin_*.exe'
|
||||
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
|
||||
|
||||
- task: SSH@0
|
||||
displayName: 'Clean up SCP symlink'
|
||||
inputs:
|
||||
sshEndpoint: 'Jellyfin Build Server'
|
||||
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.10",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
59
.copr/Makefile
Normal file
59
.copr/Makefile
Normal file
@@ -0,0 +1,59 @@
|
||||
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
|
||||
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
|
||||
|
||||
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
|
||||
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|
||||
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
||||
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
|
||||
|
||||
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
|
||||
cd deployment/fedora-package-x64; \
|
||||
SOURCE_DIR=../.. \
|
||||
WORKDIR="$${PWD}"; \
|
||||
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
|
||||
pkg_src_dir="$${WORKDIR}/pkg-src"; \
|
||||
GNU_TAR=1; \
|
||||
tar \
|
||||
--transform "s,^\.,jellyfin-$(VERSION)," \
|
||||
--exclude='.git*' \
|
||||
--exclude='**/.git' \
|
||||
--exclude='**/.hg' \
|
||||
--exclude='**/.vs' \
|
||||
--exclude='**/.vscode' \
|
||||
--exclude='deployment' \
|
||||
--exclude='**/bin' \
|
||||
--exclude='**/obj' \
|
||||
--exclude='**/.nuget' \
|
||||
--exclude='*.deb' \
|
||||
--exclude='*.rpm' \
|
||||
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
|
||||
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
|
||||
if [ $${GNU_TAR} -eq 0 ]; then \
|
||||
package_temporary_dir="$$(mktemp -d)"; \
|
||||
mkdir -p "$${package_temporary_dir}/jellyfin"; \
|
||||
tar \
|
||||
--exclude='.git*' \
|
||||
--exclude='**/.git' \
|
||||
--exclude='**/.hg' \
|
||||
--exclude='**/.vs' \
|
||||
--exclude='**/.vscode' \
|
||||
--exclude='deployment' \
|
||||
--exclude='**/bin' \
|
||||
--exclude='**/obj' \
|
||||
--exclude='**/.nuget' \
|
||||
--exclude='*.deb' \
|
||||
--exclude='*.rpm' \
|
||||
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
||||
-C $${SOURCE_DIR} ./; \
|
||||
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
|
||||
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
||||
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
|
||||
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
|
||||
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
|
||||
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
|
||||
rm -rf $${package_temporary_dir}; \
|
||||
fi; \
|
||||
rpmbuild -bs pkg-src/jellyfin.spec \
|
||||
--define "_sourcedir $$PWD/pkg-src/" \
|
||||
--define "_srcrpmdir $(outdir)"
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"name": "Development Jellyfin Server",
|
||||
"image": "mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
|
||||
"postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
|
||||
// reads the extensions list and installs them
|
||||
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/dotnet:2": {
|
||||
"version": "none",
|
||||
"dotnetRuntimeVersions": "9.0",
|
||||
"aspNetCoreRuntimeVersions": "9.0"
|
||||
},
|
||||
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
|
||||
"preserve_apt_list": false,
|
||||
"packages": [
|
||||
"libfontconfig1"
|
||||
]
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"dockerDashComposeVersion": "v2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"memory": "8gb",
|
||||
"cpus": 4
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
## configure the following for a manual install of a specific version from the repo
|
||||
|
||||
# wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb
|
||||
|
||||
# sudo apt update
|
||||
# sudo apt install -f ./ffmpeg.deb -y
|
||||
# rm ffmpeg.deb
|
||||
|
||||
|
||||
## Add the jellyfin repo
|
||||
sudo apt install curl gnupg -y
|
||||
sudo apt-get install software-properties-common -y
|
||||
sudo add-apt-repository universe -y
|
||||
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg
|
||||
export VERSION_OS="$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release )"
|
||||
export VERSION_CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )"
|
||||
export DPKG_ARCHITECTURE="$( dpkg --print-architecture )"
|
||||
cat <<EOF | sudo tee /etc/apt/sources.list.d/jellyfin.sources
|
||||
Types: deb
|
||||
URIs: https://repo.jellyfin.org/${VERSION_OS}
|
||||
Suites: ${VERSION_CODENAME}
|
||||
Components: main
|
||||
Architectures: ${DPKG_ARCHITECTURE}
|
||||
Signed-By: /etc/apt/keyrings/jellyfin.gpg
|
||||
EOF
|
||||
|
||||
sudo apt update -y
|
||||
sudo apt install jellyfin-ffmpeg7 -y
|
||||
30
.drone.yml
Normal file
30
.drone.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-debug
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: build-release
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init --recursive
|
||||
|
||||
- name: build
|
||||
image: microsoft/dotnet:2-sdk
|
||||
commands:
|
||||
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"
|
||||
|
||||
354
.editorconfig
354
.editorconfig
@@ -13,7 +13,7 @@ charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
max_line_length = off
|
||||
max_line_length = null
|
||||
|
||||
# YAML indentation
|
||||
[*.{yml,yaml}]
|
||||
@@ -22,7 +22,6 @@ indent_size = 2
|
||||
# XML indentation
|
||||
[*.{csproj,xml}]
|
||||
indent_size = 2
|
||||
|
||||
###############################
|
||||
# .NET Coding Conventions #
|
||||
###############################
|
||||
@@ -52,12 +51,11 @@ dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
|
||||
###############################
|
||||
# Naming Conventions #
|
||||
###############################
|
||||
@@ -69,7 +67,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
|
||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
||||
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected internal, private protected
|
||||
dotnet_naming_symbols.non_private_static_fields.required_modifiers = static
|
||||
|
||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||
@@ -161,7 +159,6 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
|
||||
###############################
|
||||
# C# Formatting Rules #
|
||||
###############################
|
||||
@@ -192,344 +189,9 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
# Wrapping preferences
|
||||
csharp_preserve_single_line_statements = true
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
###############################
|
||||
# C# Analyzer Rules #
|
||||
# VB Coding Conventions #
|
||||
###############################
|
||||
### ERROR #
|
||||
###########
|
||||
# error on SA1000: The keyword 'new' should be followed by a space
|
||||
dotnet_diagnostic.SA1000.severity = error
|
||||
|
||||
# error on SA1001: Commas should not be preceded by whitespace
|
||||
dotnet_diagnostic.SA1001.severity = error
|
||||
|
||||
# error on SA1106: Code should not contain empty statements
|
||||
dotnet_diagnostic.SA1106.severity = error
|
||||
|
||||
# error on SA1107: Code should not contain multiple statements on one line
|
||||
dotnet_diagnostic.SA1107.severity = error
|
||||
|
||||
# error on SA1028: Code should not contain trailing whitespace
|
||||
dotnet_diagnostic.SA1028.severity = error
|
||||
|
||||
# error on SA1117: The parameters should all be placed on the same line or each parameter should be placed on its own line
|
||||
dotnet_diagnostic.SA1117.severity = error
|
||||
|
||||
# error on SA1137: Elements should have the same indentation
|
||||
dotnet_diagnostic.SA1137.severity = error
|
||||
|
||||
# error on SA1142: Refer to tuple fields by name
|
||||
dotnet_diagnostic.SA1142.severity = error
|
||||
|
||||
# error on SA1210: Using directives should be ordered alphabetically by the namespaces
|
||||
dotnet_diagnostic.SA1210.severity = error
|
||||
|
||||
# error on SA1316: Tuple element names should use correct casing
|
||||
dotnet_diagnostic.SA1316.severity = error
|
||||
|
||||
# error on SA1414: Tuple types in signatures should have element names
|
||||
dotnet_diagnostic.SA1414.severity = error
|
||||
|
||||
# disable warning SA1513: Closing brace should be followed by blank line
|
||||
dotnet_diagnostic.SA1513.severity = error
|
||||
|
||||
# error on SA1518: File is required to end with a single newline character
|
||||
dotnet_diagnostic.SA1518.severity = error
|
||||
|
||||
# error on SA1629: Documentation text should end with a period
|
||||
dotnet_diagnostic.SA1629.severity = error
|
||||
|
||||
# error on CA1001: Types that own disposable fields should be disposable
|
||||
dotnet_diagnostic.CA1001.severity = error
|
||||
|
||||
# error on CA1012: Abstract types should not have public constructors
|
||||
dotnet_diagnostic.CA1012.severity = error
|
||||
|
||||
# error on CA1063: Implement IDisposable correctly
|
||||
dotnet_diagnostic.CA1063.severity = error
|
||||
|
||||
# error on CA1305: Specify IFormatProvider
|
||||
dotnet_diagnostic.CA1305.severity = error
|
||||
|
||||
# error on CA1307: Specify StringComparison for clarity
|
||||
dotnet_diagnostic.CA1307.severity = error
|
||||
|
||||
# error on CA1309: Use ordinal StringComparison
|
||||
dotnet_diagnostic.CA1309.severity = error
|
||||
|
||||
# error on CA1310: Specify StringComparison for correctness
|
||||
dotnet_diagnostic.CA1310.severity = error
|
||||
|
||||
# error on CA1513: Use 'ObjectDisposedException.ThrowIf' instead of explicitly throwing a new exception instance
|
||||
dotnet_diagnostic.CA1513.severity = error
|
||||
|
||||
# error on CA1725: Parameter names should match base declaration
|
||||
dotnet_diagnostic.CA1725.severity = error
|
||||
|
||||
# error on CA1725: Call async methods when in an async method
|
||||
dotnet_diagnostic.CA1727.severity = error
|
||||
|
||||
# error on CA1813: Avoid unsealed attributes
|
||||
dotnet_diagnostic.CA1813.severity = error
|
||||
|
||||
# error on CA1834: Use 'StringBuilder.Append(char)' instead of 'StringBuilder.Append(string)' when the input is a constant unit string
|
||||
dotnet_diagnostic.CA1834.severity = error
|
||||
|
||||
# error on CA1843: Do not use 'WaitAll' with a single task
|
||||
dotnet_diagnostic.CA1843.severity = error
|
||||
|
||||
# error on CA1845: Use span-based 'string.Concat'
|
||||
dotnet_diagnostic.CA1845.severity = error
|
||||
|
||||
# error on CA1849: Call async methods when in an async method
|
||||
dotnet_diagnostic.CA1849.severity = error
|
||||
|
||||
# error on CA1851: Possible multiple enumerations of IEnumerable collection
|
||||
dotnet_diagnostic.CA1851.severity = error
|
||||
|
||||
# error on CA1854: Prefer a 'TryGetValue' call over a Dictionary indexer access guarded by a 'ContainsKey' check to avoid double lookup
|
||||
dotnet_diagnostic.CA1854.severity = error
|
||||
|
||||
# error on CA1860: Avoid using 'Enumerable.Any()' extension method
|
||||
dotnet_diagnostic.CA1860.severity = error
|
||||
|
||||
# error on CA1861: Avoid constant arrays as arguments
|
||||
dotnet_diagnostic.CA1861.severity = error
|
||||
|
||||
# error on CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
||||
dotnet_diagnostic.CA1862.severity = error
|
||||
|
||||
# error on CA1863: Use 'CompositeFormat'
|
||||
dotnet_diagnostic.CA1863.severity = error
|
||||
|
||||
# error on CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method
|
||||
dotnet_diagnostic.CA1864.severity = error
|
||||
|
||||
# error on CA1865-CA1867: Use 'string.Method(char)' instead of 'string.Method(string)' for string with single char
|
||||
dotnet_diagnostic.CA1865.severity = error
|
||||
dotnet_diagnostic.CA1866.severity = error
|
||||
dotnet_diagnostic.CA1867.severity = error
|
||||
|
||||
# error on CA1868: Unnecessary call to 'Contains' for sets
|
||||
dotnet_diagnostic.CA1868.severity = error
|
||||
|
||||
# error on CA1869: Cache and reuse 'JsonSerializerOptions' instances
|
||||
dotnet_diagnostic.CA1869.severity = error
|
||||
|
||||
# error on CA1870: Use a cached 'SearchValues' instance
|
||||
dotnet_diagnostic.CA1870.severity = error
|
||||
|
||||
# error on CA1871: Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull'
|
||||
dotnet_diagnostic.CA1871.severity = error
|
||||
|
||||
# error on CA1872: Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'
|
||||
dotnet_diagnostic.CA1872.severity = error
|
||||
|
||||
# error on CA2016: Forward the CancellationToken parameter to methods that take one
|
||||
# or pass in 'CancellationToken.None' explicitly to indicate intentionally not propagating the token
|
||||
dotnet_diagnostic.CA2016.severity = error
|
||||
|
||||
# error on CA2201: Exception type System.Exception is not sufficiently specific
|
||||
dotnet_diagnostic.CA2201.severity = error
|
||||
|
||||
# error on CA2215: Dispose methods should call base class dispose
|
||||
dotnet_diagnostic.CA2215.severity = error
|
||||
|
||||
# error on CA2249: Use 'string.Contains' instead of 'string.IndexOf' to improve readability
|
||||
dotnet_diagnostic.CA2249.severity = error
|
||||
|
||||
# error on CA2254: Template should be a static expression
|
||||
dotnet_diagnostic.CA2254.severity = error
|
||||
|
||||
################
|
||||
### SUGGESTION #
|
||||
################
|
||||
# disable warning CA1014: Mark assemblies with CLSCompliantAttribute
|
||||
dotnet_diagnostic.CA1014.severity = suggestion
|
||||
|
||||
# disable warning CA1024: Use properties where appropriate
|
||||
dotnet_diagnostic.CA1024.severity = suggestion
|
||||
|
||||
# disable warning CA1031: Do not catch general exception types
|
||||
dotnet_diagnostic.CA1031.severity = suggestion
|
||||
|
||||
# disable warning CA1032: Implement standard exception constructors
|
||||
dotnet_diagnostic.CA1032.severity = suggestion
|
||||
|
||||
# disable warning CA1040: Avoid empty interfaces
|
||||
dotnet_diagnostic.CA1040.severity = suggestion
|
||||
|
||||
# disable warning CA1062: Validate arguments of public methods
|
||||
dotnet_diagnostic.CA1062.severity = suggestion
|
||||
|
||||
# TODO: enable when false positives are fixed
|
||||
# disable warning CA1508: Avoid dead conditional code
|
||||
dotnet_diagnostic.CA1508.severity = suggestion
|
||||
|
||||
# disable warning CA1515: Consider making public types internal
|
||||
dotnet_diagnostic.CA1515.severity = suggestion
|
||||
|
||||
# disable warning CA1716: Identifiers should not match keywords
|
||||
dotnet_diagnostic.CA1716.severity = suggestion
|
||||
|
||||
# disable warning CA1720: Identifiers should not contain type names
|
||||
dotnet_diagnostic.CA1720.severity = suggestion
|
||||
|
||||
# disable warning CA1724: Type names should not match namespaces
|
||||
dotnet_diagnostic.CA1724.severity = suggestion
|
||||
|
||||
# disable warning CA1805: Do not initialize unnecessarily
|
||||
dotnet_diagnostic.CA1805.severity = suggestion
|
||||
|
||||
# disable warning CA1812: internal class that is apparently never instantiated.
|
||||
# If so, remove the code from the assembly.
|
||||
# If this class is intended to contain only static members, make it static
|
||||
dotnet_diagnostic.CA1812.severity = suggestion
|
||||
|
||||
# disable warning CA1822: Member does not access instance data and can be marked as static
|
||||
dotnet_diagnostic.CA1822.severity = suggestion
|
||||
|
||||
# CA1859: Use concrete types when possible for improved performance
|
||||
dotnet_diagnostic.CA1859.severity = suggestion
|
||||
|
||||
# TODO: Enable
|
||||
# CA1861: Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array
|
||||
dotnet_diagnostic.CA1861.severity = suggestion
|
||||
|
||||
# disable warning CA2000: Dispose objects before losing scope
|
||||
dotnet_diagnostic.CA2000.severity = suggestion
|
||||
|
||||
# disable warning CA2253: Named placeholders should not be numeric values
|
||||
dotnet_diagnostic.CA2253.severity = suggestion
|
||||
|
||||
# disable warning CA5394: Do not use insecure randomness
|
||||
dotnet_diagnostic.CA5394.severity = suggestion
|
||||
|
||||
# error on CA3003: Review code for file path injection vulnerabilities
|
||||
dotnet_diagnostic.CA3003.severity = suggestion
|
||||
|
||||
# error on CA3006: Review code for process command injection vulnerabilities
|
||||
dotnet_diagnostic.CA3006.severity = suggestion
|
||||
|
||||
###############
|
||||
### DISABLED #
|
||||
###############
|
||||
# disable warning SA1009: Closing parenthesis should be followed by a space.
|
||||
dotnet_diagnostic.SA1009.severity = none
|
||||
|
||||
# disable warning SA1011: Closing square bracket should be followed by a space.
|
||||
dotnet_diagnostic.SA1011.severity = none
|
||||
|
||||
# disable warning SA1101: Prefix local calls with 'this.'
|
||||
dotnet_diagnostic.SA1101.severity = none
|
||||
|
||||
# disable warning SA1108: Block statements should not contain embedded comments
|
||||
dotnet_diagnostic.SA1108.severity = none
|
||||
|
||||
# disable warning SA1118: Parameter must not span multiple lines.
|
||||
dotnet_diagnostic.SA1118.severity = none
|
||||
|
||||
# disable warning SA1128:: Put constructor initializers on their own line
|
||||
dotnet_diagnostic.SA1128.severity = none
|
||||
|
||||
# disable warning SA1130: Use lambda syntax
|
||||
dotnet_diagnostic.SA1130.severity = none
|
||||
|
||||
# disable warning SA1200: 'using' directive must appear within a namespace declaration
|
||||
dotnet_diagnostic.SA1200.severity = none
|
||||
|
||||
# disable warning SA1202: 'public' members must come before 'private' members
|
||||
dotnet_diagnostic.SA1202.severity = none
|
||||
|
||||
# disable warning SA1204: Static members must appear before non-static members
|
||||
dotnet_diagnostic.SA1204.severity = none
|
||||
|
||||
# disable warning SA1309: Fields must not begin with an underscore
|
||||
dotnet_diagnostic.SA1309.severity = none
|
||||
|
||||
# disable warning SA1311: Static readonly fields should begin with upper-case letter
|
||||
dotnet_diagnostic.SA1311.severity = none
|
||||
|
||||
# disable warning SA1413: Use trailing comma in multi-line initializers
|
||||
dotnet_diagnostic.SA1413.severity = none
|
||||
|
||||
# disable warning SA1512: Single-line comments must not be followed by blank line
|
||||
dotnet_diagnostic.SA1512.severity = none
|
||||
|
||||
# disable warning SA1515: Single-line comment should be preceded by blank line
|
||||
dotnet_diagnostic.SA1515.severity = none
|
||||
|
||||
# disable warning SA1600: Elements should be documented
|
||||
dotnet_diagnostic.SA1600.severity = none
|
||||
|
||||
# disable warning SA1601: Partial elements should be documented
|
||||
dotnet_diagnostic.SA1601.severity = none
|
||||
|
||||
# disable warning SA1602: Enumeration items should be documented
|
||||
dotnet_diagnostic.SA1602.severity = none
|
||||
|
||||
# disable warning SA1633: The file header is missing or not located at the top of the file
|
||||
dotnet_diagnostic.SA1633.severity = none
|
||||
|
||||
# disable warning CA1054: Change the type of parameter url from string to System.Uri
|
||||
dotnet_diagnostic.CA1054.severity = none
|
||||
|
||||
# disable warning CA1055: URI return values should not be strings
|
||||
dotnet_diagnostic.CA1055.severity = none
|
||||
|
||||
# disable warning CA1056: URI properties should not be strings
|
||||
dotnet_diagnostic.CA1056.severity = none
|
||||
|
||||
# disable warning CA1303: Do not pass literals as localized parameters
|
||||
dotnet_diagnostic.CA1303.severity = none
|
||||
|
||||
# disable warning CA1308: Normalize strings to uppercase
|
||||
dotnet_diagnostic.CA1308.severity = none
|
||||
|
||||
# disable warning CA1848: Use the LoggerMessage delegates
|
||||
dotnet_diagnostic.CA1848.severity = none
|
||||
|
||||
# disable warning CA2101: Specify marshaling for P/Invoke string arguments
|
||||
dotnet_diagnostic.CA2101.severity = none
|
||||
|
||||
# disable warning CA2234: Pass System.Uri objects instead of strings
|
||||
dotnet_diagnostic.CA2234.severity = none
|
||||
|
||||
# error on RS0030: Do not used banned APIs
|
||||
dotnet_diagnostic.RS0030.severity = error
|
||||
|
||||
# disable warning IDISP001: Dispose created
|
||||
dotnet_diagnostic.IDISP001.severity = suggestion
|
||||
|
||||
# TODO: Enable when false positives are fixed
|
||||
# disable warning IDISP003: Dispose previous before re-assigning
|
||||
dotnet_diagnostic.IDISP003.severity = suggestion
|
||||
|
||||
# disable warning IDISP004: Don't ignore created IDisposable
|
||||
dotnet_diagnostic.IDISP004.severity = suggestion
|
||||
|
||||
# disable warning IDISP007: Don't dispose injected
|
||||
dotnet_diagnostic.IDISP007.severity = suggestion
|
||||
|
||||
# disable warning IDISP008: Don't assign member with injected and created disposables
|
||||
dotnet_diagnostic.IDISP008.severity = suggestion
|
||||
|
||||
[tests/**.{cs,vb}]
|
||||
# disable warning SA0001: XML comment analysis is disabled due to project configuration
|
||||
dotnet_diagnostic.SA0001.severity = none
|
||||
|
||||
# disable warning CA1707: Identifiers should not contain underscores
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
|
||||
# disable warning CA2007: Consider calling ConfigureAwait on the awaited task
|
||||
dotnet_diagnostic.CA2007.severity = none
|
||||
|
||||
# disable warning CA2234: Pass system uri objects instead of strings
|
||||
dotnet_diagnostic.CA2234.severity = suggestion
|
||||
|
||||
# disable warning xUnit1028: Test methods must have a supported return type.
|
||||
dotnet_diagnostic.xUnit1028.severity = none
|
||||
|
||||
# CA1826: Do not use Enumerable methods on indexable collections
|
||||
dotnet_diagnostic.CA1826.severity = suggestion
|
||||
[*.vb]
|
||||
# Modifier preferences
|
||||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
|
||||
|
||||
11
.github/CODEOWNERS
vendored
11
.github/CODEOWNERS
vendored
@@ -1,11 +0,0 @@
|
||||
# Joshua must review all changes to bump_version and any files it touches
|
||||
bump_version @joshuaboniface
|
||||
.github/ISSUE_TEMPLATE @joshuaboniface
|
||||
MediaBrowser.Common/MediaBrowser.Common.csproj @joshuaboniface
|
||||
Jellyfin.Data/Jellyfin.Data.csproj @joshuaboniface
|
||||
MediaBrowser.Controller/MediaBrowser.Controller.csproj @joshuaboniface
|
||||
MediaBrowser.Model/MediaBrowser.Model.csproj @joshuaboniface
|
||||
Emby.Naming/Emby.Naming.csproj @joshuaboniface
|
||||
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @joshuaboniface
|
||||
# Core must approve all changes within the repo config
|
||||
.github/ @jellyfin/core
|
||||
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a bug report
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**To Reproduce**
|
||||
<!-- Steps to reproduce the behavior: -->
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
<!-- A clear and concise description of what you expected to happen. -->
|
||||
|
||||
**Logs**
|
||||
<!-- Please paste any log errors. -->
|
||||
|
||||
**Screenshots**
|
||||
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||
|
||||
**System (please complete the following information):**
|
||||
- OS: [e.g. Docker, Debian, Windows]
|
||||
- Browser: [e.g. Firefox, Chrome, Safari]
|
||||
- Jellyfin Version: [e.g. 10.0.1]
|
||||
- Reverse proxy: [e.g. no, nginx, apache, etc.]
|
||||
|
||||
**Additional context**
|
||||
<!-- Add any other context about the problem here. -->
|
||||
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
13
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Request a new feature
|
||||
title: ''
|
||||
labels: feature-request
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**PLEASE DO NOT OPEN FEATURE REQUEST ISSUES ON GITHUB**
|
||||
|
||||
**Feature requests should be opened on our dedicated [feature request](https://features.jellyfin.org/) hub so they can be appropriately discussed and prioritized.**
|
||||
|
||||
However, if you are willing to contribute to the project by adding a new feature yourself, then please ensure that you first review our [documentation](https://docs.jellyfin.org/general/contributing/development.html) on contributing code. Once you have reviewed the documentation, feel free to come back here and open an issue here outlining your proposed approach so that it can be documented, tracked, and discussed by other team members.
|
||||
207
.github/ISSUE_TEMPLATE/issue report.yml
vendored
207
.github/ISSUE_TEMPLATE/issue report.yml
vendored
@@ -1,207 +0,0 @@
|
||||
name: Issue Report
|
||||
description: File an issue report
|
||||
labels: [bug, triage]
|
||||
type: Bug
|
||||
body:
|
||||
- type: markdown
|
||||
id: introduction
|
||||
attributes:
|
||||
value: |
|
||||
### Thank you for taking the time to report an issue!
|
||||
Please keep in mind that Jellyfin is a [free and open-source](https://jellyfin.org/docs/general/about) project, made up entirely and exclusively of **volunteers** who donate their free time to the project.
|
||||
- type: checkboxes
|
||||
id: before-posting
|
||||
attributes:
|
||||
label: "This issue respects the following points:"
|
||||
description: All conditions are **required**. Failure to comply with any of these conditions may cause your issue to be closed without comment.
|
||||
options:
|
||||
- label: This is a **bug**, not a question or a configuration issue; Please visit our [forum or chat rooms](https://jellyfin.org/contact/) first to troubleshoot with volunteers, before creating a report.
|
||||
required: true
|
||||
- label: This issue is **not** already reported on [GitHub](https://github.com/jellyfin/jellyfin/issues?q=is%3Aopen+is%3Aissue) _(I've searched it)_.
|
||||
required: true
|
||||
- label: I'm using an up to date version of Jellyfin Server stable, unstable or master; We generally do not support previous older versions. If possible, please update to the latest version before opening an issue.
|
||||
required: true
|
||||
- label: I agree to follow Jellyfin's [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct).
|
||||
required: true
|
||||
- label: This report addresses only a single issue; If you encounter multiple issues, kindly create separate reports for each one.
|
||||
required: true
|
||||
- type: markdown
|
||||
id: preliminary-information
|
||||
attributes:
|
||||
value: |
|
||||
### General preliminary information
|
||||
|
||||
Please keep the following in mind when creating this issue:
|
||||
|
||||
1. Fill in as much of the template as possible. When you are unsure about the relevancy of a section, do include the information requested in that section. Only leave out information in sections when you are completely sure about it not being relevant.
|
||||
2. Provide as much detail as possible. Do not assume other people to know what is going on.
|
||||
3. Keep everything readable and structured. Nobody enjoys reading poorly written reports that are difficult to understand.
|
||||
4. Keep an eye on your report as long as it is open, your involvement might be requested at a later moment.
|
||||
5. Keep the title short and descriptive. The title is not the place to write down a full description of the issue.
|
||||
6. When deciding to leave out information in a field, leave it blank and empty. Avoid writing things such as `n/a` for empty fields.
|
||||
- type: textarea
|
||||
id: bug-description
|
||||
attributes:
|
||||
label: Description of the bug
|
||||
description: Please provide a detailed description on the bug you encountered, in a readable and comprehensible way.
|
||||
placeholder: |
|
||||
After upgrading to version x.y.z of Jellyfin, the "login disclaimer" is showing incorrect text. It appears to me that it is appending the server name to the end of the login disclaimer, and showing that to a user. It might be a regression from pull request x. I have tried rebooting my host as well as my container multiple times. I tested this functionality on different clients, and it happens to all the tested clients (client x, y, z), that support the login disclaimer functionality. This makes me believe it is a server side issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: repro-steps
|
||||
attributes:
|
||||
label: Reproduction steps
|
||||
description: Reproduction steps should be complete and self-contained. Anyone can reproduce this issue by following these steps. Furthermore, the steps should be clear and easy to follow.
|
||||
placeholder: |
|
||||
1. Sign in on the Jellyfin web client, with an admin account, using a browser of your choice.
|
||||
2. Navigate to the dashboard.
|
||||
3. Select "general".
|
||||
4. Change the login disclaimer to something like "I am a cool disclaimer!"
|
||||
5. Save the settings.
|
||||
6. Sign out.
|
||||
7. Make sure you are on the sign in screen. Otherwise, navigate to the sign in screen manually.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: What is the current _bug_ behavior?
|
||||
description: Write down the incorrect behavior that currently happens after following the reproduction steps.
|
||||
placeholder: |
|
||||
The login disclaimer on the sign in screen has the server name appended to the text. The text shown is: "I am a cool disclaimer!jellyfinserver".
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: What is the expected _correct_ behavior?
|
||||
description: Write down the correct expected behavior that is supposed to happen after following the reproduction steps.
|
||||
placeholder: |
|
||||
The login disclaimer on the sign in screen should only show the configured text. The text that should be shown is: "I am a cool disclaimer!".
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: Jellyfin Server version
|
||||
description: What version of Jellyfin are you using?
|
||||
options:
|
||||
- 10.10.0+
|
||||
- Master
|
||||
- Unstable
|
||||
- Older*
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version-master
|
||||
attributes:
|
||||
label: "Specify commit id"
|
||||
description: Fill in this field in case the option 'master' is selected. Provide the commit id it was built on.
|
||||
placeholder: |
|
||||
610e56baafc3011e1bfa043bdabb567bda0c2ab0
|
||||
- type: input
|
||||
id: version-unstable
|
||||
attributes:
|
||||
label: "Specify unstable release number"
|
||||
description: Fill in this field in case the option 'unstable' is selected. Provide the unstable release number.
|
||||
placeholder: |
|
||||
2024050906
|
||||
- type: input
|
||||
id: version-older
|
||||
attributes:
|
||||
label: "Specify version number"
|
||||
description: Fill in this field in case the option 'older' is selected. Provide the version number.
|
||||
placeholder: |
|
||||
x.y.z
|
||||
- type: input
|
||||
id: build-version
|
||||
attributes:
|
||||
label: "Specify the build version"
|
||||
description: Please provide the build version that is shown in the dashboard.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: environment-information
|
||||
attributes:
|
||||
label: Environment
|
||||
description: |
|
||||
Accurately fill in as much environment details as possible. If a certain environment field is not shown in the template below, but you consider useful information, please include it.
|
||||
Examples:
|
||||
- **OS**: [e.g. Debian 11, Windows 10]
|
||||
- **Linux Kernel**: [e.g. none, 5.15, 6.1, etc.]
|
||||
- **Virtualization**: [e.g. Docker, KVM, LXC]
|
||||
- **Clients**: [Browser, Android, Fire Stick, etc.]
|
||||
- **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13]
|
||||
- **FFmpeg Version**: [e.g. 5.1.2-Jellyfin]
|
||||
- **Playback**: [Direct Play, Remux, Direct Stream, Transcode]
|
||||
- **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.]
|
||||
- **GPU Model**: [e.g. none, UHD630, GTX1050, etc.]
|
||||
- **Installed Plugins**: [e.g. none, Fanart, Anime, etc.]
|
||||
- **Reverse Proxy**: [e.g. none, nginx, apache, etc.]
|
||||
- **Base URL**: [e.g. none, yes: /example]
|
||||
- **Networking**: [e.g. Host, Bridge/NAT]
|
||||
- **Jellyfin Data Storage**: [e.g. local SATA SSD, local HDD]
|
||||
- **Media Storage**: [e.g. Local HDD, SMB Share]
|
||||
- **External Integrations**: [e.g. Jellystat, Jellyseerr]
|
||||
value: |
|
||||
- OS:
|
||||
- Linux Kernel:
|
||||
- Virtualization:
|
||||
- Clients:
|
||||
- Browser:
|
||||
- FFmpeg Version:
|
||||
- Playback Method:
|
||||
- Hardware Acceleration:
|
||||
- GPU Model:
|
||||
- Plugins:
|
||||
- Reverse Proxy:
|
||||
- Base URL:
|
||||
- Networking:
|
||||
- Jellyfin Data Storage:
|
||||
- Media Storage:
|
||||
- External Integrations:
|
||||
render: markdown
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
id: general-information-logs
|
||||
attributes:
|
||||
value: |
|
||||
When providing logs, please keep the following things in mind:
|
||||
1. **DO NOT** use external paste services. If logs are too large to paste into the field, upload them as text files.
|
||||
2. Please provide complete logs.
|
||||
- For server logs, ensure to capture all relevant information, encompassing both the events leading up to and following the occurrence of the issue. Typically, providing 10 *lines preceding and succeeding* the problem should be adequate.
|
||||
- For ffmpeg logs, please provide the entire file unmodified.
|
||||
3. Please do not run logs through any translation program. We exclusively accept raw, untranslated logs. Particularly exercise caution if your browser automatically translates pages by default.
|
||||
- Do not forget to censor out personal information such as public IP addresses.
|
||||
4. Please do not include logs as screenshots, with the only exception being client logs in browsers.
|
||||
- type: textarea
|
||||
id: jellyfin-logs
|
||||
attributes:
|
||||
label: Jellyfin logs
|
||||
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
|
||||
render: shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: ffmpeg-logs
|
||||
attributes:
|
||||
label: FFmpeg logs
|
||||
description: Relevant FFmpeg log output. This can be found in Dashboard > Logs > FFmpeg*.log. This field is considered mandatory for transcoding related issues. It's also important to include the specific codec details.
|
||||
render: shell
|
||||
- type: textarea
|
||||
id: browser-logs
|
||||
attributes:
|
||||
label: Client / Browser logs
|
||||
description: Access browser logs by using the F12 to bring up the console. Screenshots are typically easier to read than raw logs. For clients such as Android or iOS, please see our documentation.
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Relevant screenshots or videos
|
||||
description: Attach relevant screenshots or videos related to this report.
|
||||
- type: textarea
|
||||
id: additional-information
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Any additional information that might be useful to this issue.
|
||||
32
.github/ISSUE_TEMPLATE/media_playback.md
vendored
Normal file
32
.github/ISSUE_TEMPLATE/media_playback.md
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
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 message from during the playback issue, for example the ffmpeg command line can be very useful. -->
|
||||
|
||||
**Stats for Nerds Screenshots**
|
||||
<!-- If available, add screenshots of the stats for nerds screen to help show the issue problem. -->
|
||||
|
||||
**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]
|
||||
|
||||
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>jellyfin/.github//renovate-presets/dotnet"
|
||||
]
|
||||
}
|
||||
22
.github/stale.yml
vendored
Normal file
22
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 90
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 14
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- regression
|
||||
- security
|
||||
- dotnet-3.0-future
|
||||
- roadmap
|
||||
- future
|
||||
- feature
|
||||
- enhancement
|
||||
# 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: >
|
||||
Issues go stale after 90d of inactivity. Mark the issue as fresh by adding a comment or commit. Stale issues close after an additional 14d of inactivity.
|
||||
If this issue is safe to close now please do so.
|
||||
If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
||||
37
.github/workflows/ci-codeql-analysis.yml
vendored
37
.github/workflows/ci-codeql-analysis.yml
vendored
@@ -1,37 +0,0 @@
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '24 2 * * 4'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'csharp' ]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@4e94bd11f71e507f7f87df81788dff88d1dacbfb # v4.31.0
|
||||
159
.github/workflows/ci-compat.yml
vendored
159
.github/workflows/ci-compat.yml
vendored
@@ -1,159 +0,0 @@
|
||||
name: ABI Compatibility
|
||||
on:
|
||||
pull_request_target:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
abi-head:
|
||||
name: ABI - HEAD
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: abi-head
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: out/
|
||||
|
||||
abi-base:
|
||||
name: ABI - BASE
|
||||
if: ${{ github.base_ref != '' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Checkout common ancestor
|
||||
env:
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
run: |
|
||||
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
|
||||
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
|
||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
||||
git checkout --progress --force $ANCESTOR_REF
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: abi-base
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: out/
|
||||
|
||||
abi-diff:
|
||||
permissions:
|
||||
pull-requests: write # to create or update comment (peter-evans/create-or-update-comment)
|
||||
|
||||
name: ABI - Difference
|
||||
if: ${{ github.event_name == 'pull_request_target' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- abi-head
|
||||
- abi-base
|
||||
|
||||
steps:
|
||||
- name: Download abi-head
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: abi-head
|
||||
path: abi-head
|
||||
|
||||
- name: Download abi-base
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: abi-base
|
||||
path: abi-base
|
||||
|
||||
- name: Setup ApiCompat
|
||||
run: |
|
||||
dotnet tool install --global Microsoft.DotNet.ApiCompat.Tool
|
||||
|
||||
- name: Run ApiCompat
|
||||
id: diff
|
||||
run: |
|
||||
{
|
||||
echo 'body<<EOF'
|
||||
for file in Jellyfin.Data.dll MediaBrowser.Common.dll MediaBrowser.Controller.dll MediaBrowser.Model.dll Emby.Naming.dll Jellyfin.Extensions.dll Jellyfin.MediaEncoding.Keyframes.dll Jellyfin.Database.Implementations.dll; do
|
||||
COMPAT_OUTPUT="$( { apicompat --left ./abi-base/${file} --right ./abi-head/${file}; } 2>&1 )"
|
||||
if [ "APICompat ran successfully without finding any breaking changes." != "${COMPAT_OUTPUT}" ]; then
|
||||
printf "\n${file}\n${COMPAT_OUTPUT}\n"
|
||||
fi
|
||||
done
|
||||
echo EOF
|
||||
} >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Find difference comment
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
direction: last
|
||||
body-includes: abi-diff-workflow-comment
|
||||
|
||||
- name: Reply or edit difference comment (changed)
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
if: ${{ steps.diff.outputs.body != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
body: |
|
||||
<!--abi-diff-workflow-comment-->
|
||||
<details>
|
||||
<summary>ABI Difference</summary>
|
||||
|
||||
```
|
||||
${{ steps.diff.outputs.body }}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
- name: Reply or edit difference comment (unchanged)
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
body: |
|
||||
<!--abi-diff-workflow-comment-->
|
||||
<details>
|
||||
<summary>ABI Difference</summary>
|
||||
|
||||
No changes to the ABI found. See history of this comment for previous changes.
|
||||
|
||||
</details>
|
||||
271
.github/workflows/ci-openapi.yml
vendored
271
.github/workflows/ci-openapi.yml
vendored
@@ -1,271 +0,0 @@
|
||||
name: OpenAPI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- 'v*'
|
||||
pull_request_target:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
openapi-head:
|
||||
name: OpenAPI - HEAD
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json
|
||||
|
||||
openapi-base:
|
||||
name: OpenAPI - BASE
|
||||
if: ${{ github.base_ref != '' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
fetch-depth: 0
|
||||
- name: Checkout common ancestor
|
||||
env:
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
run: |
|
||||
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
|
||||
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
|
||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
||||
git checkout --progress --force $ANCESTOR_REF
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
- name: Generate openapi.json
|
||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||
- name: Upload openapi.json
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: openapi-base
|
||||
retention-days: 14
|
||||
if-no-files-found: error
|
||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json
|
||||
|
||||
openapi-diff:
|
||||
permissions:
|
||||
pull-requests: write # to create or update comment (peter-evans/create-or-update-comment)
|
||||
|
||||
name: OpenAPI - Difference
|
||||
if: ${{ github.event_name == 'pull_request_target' }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- openapi-head
|
||||
- openapi-base
|
||||
steps:
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Download openapi-base
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: openapi-base
|
||||
path: openapi-base
|
||||
- name: Workaround openapi-diff issue
|
||||
run: |
|
||||
sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
|
||||
sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
|
||||
- name: Calculate OpenAPI difference
|
||||
uses: docker://openapitools/openapi-diff
|
||||
continue-on-error: true
|
||||
with:
|
||||
args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
|
||||
- id: read-diff
|
||||
name: Read openapi-diff output
|
||||
run: |
|
||||
# Read and fix markdown
|
||||
body=$(cat openapi-changes.md)
|
||||
# Write to workflow summary
|
||||
echo "$body" >> $GITHUB_STEP_SUMMARY
|
||||
# Set ApiChanged var
|
||||
if [ "$body" != '' ]; then
|
||||
echo "ApiChanged=1" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ApiChanged=0" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
# Add header/footer for diff comment
|
||||
echo '<!--openapi-diff-workflow-comment-->' > openapi-changes-reply.md
|
||||
echo "<details>" >> openapi-changes-reply.md
|
||||
echo "<summary>Changes in OpenAPI specification found. Expand to see details.</summary>" >> openapi-changes-reply.md
|
||||
echo "" >> openapi-changes-reply.md
|
||||
echo "$body" >> openapi-changes-reply.md
|
||||
echo "" >> openapi-changes-reply.md
|
||||
echo "</details>" >> openapi-changes-reply.md
|
||||
- name: Find difference comment
|
||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
direction: last
|
||||
body-includes: openapi-diff-workflow-comment
|
||||
- name: Reply or edit difference comment (changed)
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
if: ${{ steps.read-diff.outputs.ApiChanged == '1' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
body-path: openapi-changes-reply.md
|
||||
- name: Edit difference comment (unchanged)
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!--openapi-diff-workflow-comment-->
|
||||
|
||||
No changes to OpenAPI specification found. See history of this comment for previous changes.
|
||||
|
||||
publish-unstable:
|
||||
name: OpenAPI - Publish Unstable Spec
|
||||
if: ${{ github.event_name != 'pull_request_target' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- openapi-head
|
||||
steps:
|
||||
- name: Set unstable dated version
|
||||
id: version
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Upload openapi.json (unstable) to repository server
|
||||
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
key: "${{ secrets.REPO_KEY }}"
|
||||
source: openapi-head/openapi.json
|
||||
strip_components: 1
|
||||
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||
- name: Move openapi.json (unstable) into place
|
||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
key: "${{ secrets.REPO_KEY }}"
|
||||
debug: false
|
||||
script_stop: false
|
||||
script: |
|
||||
if ! test -d /run/workflows; then
|
||||
sudo mkdir -p /run/workflows
|
||||
sudo chown ${{ secrets.REPO_USER }} /run/workflows
|
||||
fi
|
||||
(
|
||||
flock -x -w 300 200 || exit 1
|
||||
TGT_DIR="/srv/repository/main/openapi"
|
||||
LAST_SPEC="$( ls -lt ${TGT_DIR}/unstable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
|
||||
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
|
||||
if diff /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/${LAST_SPEC} &>/dev/null; then
|
||||
rm -r /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
|
||||
exit 0
|
||||
fi
|
||||
# Move new spec into place
|
||||
sudo mv /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
|
||||
# Delete previous jellyfin-openapi-unstable_previous.json
|
||||
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
|
||||
# Move current jellyfin-openapi-unstable.json symlink to jellyfin-openapi-unstable_previous.json
|
||||
sudo mv ${TGT_DIR}/jellyfin-openapi-unstable.json ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
|
||||
# Create new jellyfin-openapi-unstable.json symlink
|
||||
sudo ln -s unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-unstable.json
|
||||
# Check that the previous openapi unstable spec link is correct
|
||||
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-unstable_previous.json )" != "unstable/${LAST_SPEC}" ]]; then
|
||||
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
|
||||
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
|
||||
fi
|
||||
) 200>/run/workflows/openapi-unstable.lock
|
||||
|
||||
publish-stable:
|
||||
name: OpenAPI - Publish Stable Spec
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- openapi-head
|
||||
steps:
|
||||
- name: Set version number
|
||||
id: version
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Upload openapi.json (stable) to repository server
|
||||
uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
key: "${{ secrets.REPO_KEY }}"
|
||||
source: openapi-head/openapi.json
|
||||
strip_components: 1
|
||||
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||
- name: Move openapi.json (stable) into place
|
||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
key: "${{ secrets.REPO_KEY }}"
|
||||
debug: false
|
||||
script_stop: false
|
||||
script: |
|
||||
if ! test -d /run/workflows; then
|
||||
sudo mkdir -p /run/workflows
|
||||
sudo chown ${{ secrets.REPO_USER }} /run/workflows
|
||||
fi
|
||||
(
|
||||
flock -x -w 300 200 || exit 1
|
||||
TGT_DIR="/srv/repository/main/openapi"
|
||||
LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
|
||||
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
|
||||
if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
|
||||
rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
|
||||
exit 0
|
||||
fi
|
||||
# Move new spec into place
|
||||
sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
|
||||
# Delete previous jellyfin-openapi-stable_previous.json
|
||||
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
|
||||
# Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
|
||||
sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
|
||||
# Create new jellyfin-openapi-stable.json symlink
|
||||
sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
|
||||
# Check that the previous openapi stable spec link is correct
|
||||
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
|
||||
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
|
||||
sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
|
||||
fi
|
||||
) 200>/run/workflows/openapi-stable.lock
|
||||
45
.github/workflows/ci-tests.yml
vendored
45
.github/workflows/ci-tests.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# Run tests against the forked branch, but
|
||||
# do not allow access to secrets
|
||||
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
SDK_VERSION: "9.0.x"
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||
fail-fast: false
|
||||
|
||||
runs-on: "${{ matrix.os }}"
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||
with:
|
||||
dotnet-version: ${{ env.SDK_VERSION }}
|
||||
|
||||
- name: Run DotNet CLI Tests
|
||||
run: >
|
||||
dotnet test Jellyfin.sln
|
||||
--configuration Release
|
||||
--collect:"XPlat Code Coverage"
|
||||
--settings tests/coverletArgs.runsettings
|
||||
--verbosity minimal
|
||||
|
||||
- name: Merge code coverage results
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@f3c6b3f8a29686284ef7a7cf6dccb79a01d98444 # v5.4.18
|
||||
with:
|
||||
reports: "**/coverage.cobertura.xml"
|
||||
targetdir: "merged/"
|
||||
reporttypes: "Cobertura"
|
||||
|
||||
# TODO - which action / tool to use to publish code coverage results?
|
||||
# - name: Publish code coverage results
|
||||
60
.github/workflows/commands.yml
vendored
60
.github/workflows/commands.yml
vendored
@@ -1,60 +0,0 @@
|
||||
name: Commands
|
||||
on:
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
- edited
|
||||
pull_request_target:
|
||||
types:
|
||||
- labeled
|
||||
- synchronize
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
rebase:
|
||||
name: Rebase
|
||||
if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '@jellyfin-bot rebase') && github.event.comment.author_association == 'MEMBER'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: '+1'
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
rename:
|
||||
name: Rename
|
||||
if: contains(github.event.comment.body, '@jellyfin-bot rename') && github.event.comment.author_association == 'MEMBER'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: pull in script
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
repository: jellyfin/jellyfin-triage-script
|
||||
- name: install python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.14'
|
||||
cache: 'pip'
|
||||
- name: install python packages
|
||||
run: pip install -r rename/requirements.txt
|
||||
- name: run rename script
|
||||
run: python3 rename.py
|
||||
working-directory: ./rename
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
ISSUE: ${{ github.event.issue.number }}
|
||||
COMMENT_ID: ${{ github.event.comment.id }}
|
||||
35
.github/workflows/issue-stale.yml
vendored
35
.github/workflows/issue-stale.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Stale Issue Labeler
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
issues:
|
||||
name: Check for stale issues
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
ascending: true
|
||||
days-before-stale: 120
|
||||
days-before-pr-stale: -1
|
||||
days-before-close: 21
|
||||
days-before-pr-close: -1
|
||||
operations-per-run: 500
|
||||
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
||||
stale-issue-label: stale
|
||||
stale-issue-message: |-
|
||||
This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs.
|
||||
|
||||
If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact).
|
||||
close-issue-message: |-
|
||||
This issue was closed due to inactivity.
|
||||
29
.github/workflows/issue-template-check.yml
vendored
29
.github/workflows/issue-template-check.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Check Issue Template
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
jobs:
|
||||
check_issue:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: pull in script
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
repository: jellyfin/jellyfin-triage-script
|
||||
- name: install python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.14'
|
||||
cache: 'pip'
|
||||
- name: install python packages
|
||||
run: pip install -r main-repo-triage/requirements.txt
|
||||
- name: check and comment issue
|
||||
working-directory: ./main-repo-triage
|
||||
run: python3 single_issue_gha.py
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
ISSUE: ${{ github.event.issue.number }}
|
||||
65
.github/workflows/project-automation.yml
vendored
65
.github/workflows/project-automation.yml
vendored
@@ -1,65 +0,0 @@
|
||||
name: Project Automation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
issue_comment:
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
project:
|
||||
name: Project board
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||
steps:
|
||||
- name: Remove from 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Current Release
|
||||
action: delete
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Release Next' project
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Release Next
|
||||
column: In progress
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add to 'Current Release' project
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
|
||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Current Release
|
||||
column: In progress
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Check number of comments from the team member
|
||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
|
||||
id: member_comments
|
||||
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
||||
|
||||
- name: Move issue to needs triage
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
|
||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Issue Triage for Main Repo
|
||||
column: Needs triage
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
- name: Add issue to triage project
|
||||
uses: alex-page/github-project-automation-plus@303f24a24c67ce7adf565a07e96720faf126fe36 # v0.9.0
|
||||
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
||||
continue-on-error: true
|
||||
with:
|
||||
project: Issue Triage for Main Repo
|
||||
column: Pending response
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
23
.github/workflows/pull-request-conflict.yml
vendored
23
.github/workflows/pull-request-conflict.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: Merge Conflict Labeler
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request_target:
|
||||
issue_comment:
|
||||
|
||||
permissions: {}
|
||||
jobs:
|
||||
label:
|
||||
name: Labeling
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'jellyfin/jellyfin' && github.event.issue.pull_request }}
|
||||
steps:
|
||||
- name: Apply label
|
||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
||||
with:
|
||||
dirtyLabel: 'merge conflict'
|
||||
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
|
||||
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
||||
30
.github/workflows/pull-request-stale.yaml
vendored
30
.github/workflows/pull-request-stale.yaml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Stale PR Check
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 */12 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
prs-stale-conflicts:
|
||||
name: Check PRs with merge conflicts
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||
steps:
|
||||
- uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
|
||||
with:
|
||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
ascending: true
|
||||
operations-per-run: 150
|
||||
# The merge conflict action will remove the label when updated
|
||||
remove-stale-when-updated: false
|
||||
days-before-stale: -1
|
||||
days-before-close: 90
|
||||
days-before-issue-close: -1
|
||||
stale-pr-label: merge conflict
|
||||
close-pr-message: |-
|
||||
This PR has been closed due to having unresolved merge conflicts.
|
||||
82
.github/workflows/release-bump-version.yaml
vendored
82
.github/workflows/release-bump-version.yaml
vendored
@@ -1,82 +0,0 @@
|
||||
name: '🆙 Auto bump_version'
|
||||
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
TAG_BRANCH:
|
||||
required: true
|
||||
description: release-x.y.z
|
||||
NEXT_VERSION:
|
||||
required: true
|
||||
description: x.y.z
|
||||
|
||||
jobs:
|
||||
auto_bump_version:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'release' && !contains(github.event.release.tag_name, 'rc') }}
|
||||
env:
|
||||
TAG_BRANCH: ${{ github.event.release.target_commitish }}
|
||||
steps:
|
||||
- name: Wait for deploy checks to finish
|
||||
uses: jitterbit/await-check-suites@292a541bb7618078395b2ce711a0d89cfb8a568a # v1
|
||||
with:
|
||||
ref: ${{ env.TAG_BRANCH }}
|
||||
intervalSeconds: 60
|
||||
timeoutSeconds: 3600
|
||||
|
||||
- name: Setup YQ
|
||||
uses: chrisdickinson/setup-yq@latest
|
||||
with:
|
||||
yq-version: v4.9.8
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ env.TAG_BRANCH }}
|
||||
|
||||
- name: Setup EnvVars
|
||||
run: |-
|
||||
CURRENT_VERSION=$(yq e '.version' build.yaml)
|
||||
CURRENT_MAJOR_MINOR=${CURRENT_VERSION%.*}
|
||||
CURRENT_PATCH=${CURRENT_VERSION##*.}
|
||||
echo "CURRENT_VERSION=${CURRENT_VERSION}" >> $GITHUB_ENV
|
||||
echo "CURRENT_MAJOR_MINOR=${CURRENT_MAJOR_MINOR}" >> $GITHUB_ENV
|
||||
echo "CURRENT_PATCH=${CURRENT_PATCH}" >> $GITHUB_ENV
|
||||
echo "NEXT_VERSION=${CURRENT_MAJOR_MINOR}.$(($CURRENT_PATCH + 1))" >> $GITHUB_ENV
|
||||
|
||||
- name: Run bump_version
|
||||
run: ./bump_version ${{ env.NEXT_VERSION }}
|
||||
|
||||
- name: Commit Changes
|
||||
run: |-
|
||||
git config user.name "jellyfin-bot"
|
||||
git config user.email "team@jellyfin.org"
|
||||
git checkout ${{ env.TAG_BRANCH }}
|
||||
git commit -am "Bump version to ${{ env.NEXT_VERSION }}"
|
||||
git push origin ${{ env.TAG_BRANCH }}
|
||||
|
||||
manual_bump_version:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
env:
|
||||
TAG_BRANCH: ${{ github.event.inputs.TAG_BRANCH }}
|
||||
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
ref: ${{ env.TAG_BRANCH }}
|
||||
|
||||
- name: Run bump_version
|
||||
run: ./bump_version ${{ env.NEXT_VERSION }}
|
||||
|
||||
- name: Commit Changes
|
||||
run: |-
|
||||
git config user.name "jellyfin-bot"
|
||||
git config user.email "team@jellyfin.org"
|
||||
git checkout ${{ env.TAG_BRANCH }}
|
||||
git commit -am "Bump version to ${{ env.NEXT_VERSION }}"
|
||||
git push origin ${{ env.TAG_BRANCH }}
|
||||
29
.gitignore
vendored
29
.gitignore
vendored
@@ -150,6 +150,8 @@ publish/
|
||||
*.pubxml
|
||||
|
||||
# NuGet Packages Directory
|
||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||
# packages/
|
||||
dlls/
|
||||
dllssigned/
|
||||
|
||||
@@ -164,6 +166,7 @@ AppPackages/
|
||||
sql/
|
||||
*.Cache
|
||||
ClientBin/
|
||||
[Ss]tyle[Cc]op.*
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
@@ -241,14 +244,14 @@ pip-log.txt
|
||||
#########################
|
||||
|
||||
# Artifacts for debian-x64
|
||||
debian/.debhelper/
|
||||
debian/*.debhelper
|
||||
debian/debhelper-build-stamp
|
||||
debian/files
|
||||
debian/jellyfin.substvars
|
||||
debian/jellyfin/
|
||||
deployment/debian-package-x64/pkg-src/.debhelper/
|
||||
deployment/debian-package-x64/pkg-src/*.debhelper
|
||||
deployment/debian-package-x64/pkg-src/debhelper-build-stamp
|
||||
deployment/debian-package-x64/pkg-src/files
|
||||
deployment/debian-package-x64/pkg-src/jellyfin.substvars
|
||||
deployment/debian-package-x64/pkg-src/jellyfin/
|
||||
# Don't ignore the debian/bin folder
|
||||
!debian/bin/
|
||||
!deployment/debian-package-x64/pkg-src/bin/
|
||||
|
||||
deployment/**/dist/
|
||||
deployment/**/pkg-dist/
|
||||
@@ -265,15 +268,3 @@ doc/
|
||||
# Deployment artifacts
|
||||
dist
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
# BenchmarkDotNet artifacts
|
||||
BenchmarkDotNet.Artifacts
|
||||
|
||||
# Ignore web artifacts from native builds
|
||||
web/
|
||||
web-src.*
|
||||
apiclient/generated
|
||||
|
||||
# Omnisharp crash logs
|
||||
mono_crash.*.json
|
||||
|
||||
13
.vscode/extensions.json
vendored
13
.vscode/extensions.json
vendored
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"editorconfig.editorconfig",
|
||||
"github.vscode-github-actions",
|
||||
"ms-dotnettools.vscode-dotnet-runtime",
|
||||
"ms-dotnettools.csdevkit",
|
||||
"alexcvzz.vscode-sqlite"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
|
||||
]
|
||||
}
|
||||
50
.vscode/launch.json
vendored
50
.vscode/launch.json
vendored
@@ -1,54 +1,28 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": ".NET Launch (console)",
|
||||
"name": ".NET Core Launch (console)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/netcoreapp2.1/jellyfin.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "Overriding address\\(es\\) \\'(https?:\\S+)\\'",
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Launch (nowebclient)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
|
||||
"args": ["--nowebclient"],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
// For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "ghcs .NET Launch (nowebclient, ffmpeg)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net9.0/jellyfin.dll",
|
||||
"args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": ".NET Attach",
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach",
|
||||
"processId": "${command:pickProcess}"
|
||||
}
|
||||
],
|
||||
"env": {
|
||||
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||
}
|
||||
}
|
||||
,]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"dotnet.preferVisualStudioCodeFileSystemWatcher": true
|
||||
}
|
||||
19
.vscode/tasks.json
vendored
19
.vscode/tasks.json
vendored
@@ -10,21 +10,6 @@
|
||||
"${workspaceFolder}/Jellyfin.Server/Jellyfin.Server.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "api tests",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"test",
|
||||
"${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
],
|
||||
"options": {
|
||||
"env": {
|
||||
"DOTNET_CLI_TELEMETRY_OPTOUT": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
17
BDInfo/BDInfo.csproj
Normal file
17
BDInfo/BDInfo.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
33
BDInfo/BDInfoSettings.cs
Normal file
33
BDInfo/BDInfoSettings.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
class BDInfoSettings
|
||||
{
|
||||
public static bool GenerateStreamDiagnostics => true;
|
||||
|
||||
public static bool EnableSSIF => true;
|
||||
|
||||
public static bool AutosaveReport => false;
|
||||
|
||||
public static bool GenerateFrameDataFile => false;
|
||||
|
||||
public static bool FilterLoopingPlaylists => true;
|
||||
|
||||
public static bool FilterShortPlaylists => false;
|
||||
|
||||
public static int FilterShortPlaylistsValue => 0;
|
||||
|
||||
public static bool UseImagePrefix => false;
|
||||
|
||||
public static string UseImagePrefixValue => null;
|
||||
|
||||
/// <summary>
|
||||
/// Setting this to false throws an IComparer error on some discs.
|
||||
/// </summary>
|
||||
public static bool KeepStreamOrder => true;
|
||||
|
||||
public static bool GenerateTextSummary => false;
|
||||
|
||||
public static string LastPath => string.Empty;
|
||||
}
|
||||
}
|
||||
449
BDInfo/BDROM.cs
Normal file
449
BDInfo/BDROM.cs
Normal file
@@ -0,0 +1,449 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public class BDROM
|
||||
{
|
||||
public FileSystemMetadata DirectoryRoot = null;
|
||||
public FileSystemMetadata DirectoryBDMV = null;
|
||||
public FileSystemMetadata DirectoryBDJO = null;
|
||||
public FileSystemMetadata DirectoryCLIPINF = null;
|
||||
public FileSystemMetadata DirectoryPLAYLIST = null;
|
||||
public FileSystemMetadata DirectorySNP = null;
|
||||
public FileSystemMetadata DirectorySSIF = null;
|
||||
public FileSystemMetadata DirectorySTREAM = null;
|
||||
|
||||
public string VolumeLabel = null;
|
||||
public ulong Size = 0;
|
||||
public bool IsBDPlus = false;
|
||||
public bool IsBDJava = false;
|
||||
public bool IsDBOX = false;
|
||||
public bool IsPSP = false;
|
||||
public bool Is3D = false;
|
||||
public bool Is50Hz = false;
|
||||
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public Dictionary<string, TSPlaylistFile> PlaylistFiles =
|
||||
new Dictionary<string, TSPlaylistFile>();
|
||||
public Dictionary<string, TSStreamClipFile> StreamClipFiles =
|
||||
new Dictionary<string, TSStreamClipFile>();
|
||||
public Dictionary<string, TSStreamFile> StreamFiles =
|
||||
new Dictionary<string, TSStreamFile>();
|
||||
public Dictionary<string, TSInterleavedFile> InterleavedFiles =
|
||||
new Dictionary<string, TSInterleavedFile>();
|
||||
|
||||
public delegate bool OnStreamClipFileScanError(
|
||||
TSStreamClipFile streamClipFile, Exception ex);
|
||||
|
||||
public event OnStreamClipFileScanError StreamClipFileScanError;
|
||||
|
||||
public delegate bool OnStreamFileScanError(
|
||||
TSStreamFile streamClipFile, Exception ex);
|
||||
|
||||
public event OnStreamFileScanError StreamFileScanError;
|
||||
|
||||
public delegate bool OnPlaylistFileScanError(
|
||||
TSPlaylistFile playlistFile, Exception ex);
|
||||
|
||||
public event OnPlaylistFileScanError PlaylistFileScanError;
|
||||
|
||||
public BDROM(string path, IFileSystem fileSystem)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
_fileSystem = fileSystem;
|
||||
//
|
||||
// Locate BDMV directories.
|
||||
//
|
||||
|
||||
DirectoryBDMV =
|
||||
GetDirectoryBDMV(path);
|
||||
|
||||
if (DirectoryBDMV == null)
|
||||
{
|
||||
throw new Exception("Unable to locate BD structure.");
|
||||
}
|
||||
|
||||
DirectoryRoot =
|
||||
_fileSystem.GetDirectoryInfo(Path.GetDirectoryName(DirectoryBDMV.FullName));
|
||||
DirectoryBDJO =
|
||||
GetDirectory("BDJO", DirectoryBDMV, 0);
|
||||
DirectoryCLIPINF =
|
||||
GetDirectory("CLIPINF", DirectoryBDMV, 0);
|
||||
DirectoryPLAYLIST =
|
||||
GetDirectory("PLAYLIST", DirectoryBDMV, 0);
|
||||
DirectorySNP =
|
||||
GetDirectory("SNP", DirectoryRoot, 0);
|
||||
DirectorySTREAM =
|
||||
GetDirectory("STREAM", DirectoryBDMV, 0);
|
||||
DirectorySSIF =
|
||||
GetDirectory("SSIF", DirectorySTREAM, 0);
|
||||
|
||||
if (DirectoryCLIPINF == null
|
||||
|| DirectoryPLAYLIST == null)
|
||||
{
|
||||
throw new Exception("Unable to locate BD structure.");
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize basic disc properties.
|
||||
//
|
||||
|
||||
VolumeLabel = GetVolumeLabel(DirectoryRoot);
|
||||
Size = (ulong)GetDirectorySize(DirectoryRoot);
|
||||
|
||||
if (null != GetDirectory("BDSVM", DirectoryRoot, 0))
|
||||
{
|
||||
IsBDPlus = true;
|
||||
}
|
||||
if (null != GetDirectory("SLYVM", DirectoryRoot, 0))
|
||||
{
|
||||
IsBDPlus = true;
|
||||
}
|
||||
if (null != GetDirectory("ANYVM", DirectoryRoot, 0))
|
||||
{
|
||||
IsBDPlus = true;
|
||||
}
|
||||
|
||||
if (DirectoryBDJO != null &&
|
||||
_fileSystem.GetFilePaths(DirectoryBDJO.FullName).Any())
|
||||
{
|
||||
IsBDJava = true;
|
||||
}
|
||||
|
||||
if (DirectorySNP != null &&
|
||||
GetFilePaths(DirectorySNP.FullName, ".mnv").Any())
|
||||
{
|
||||
IsPSP = true;
|
||||
}
|
||||
|
||||
if (DirectorySSIF != null &&
|
||||
_fileSystem.GetFilePaths(DirectorySSIF.FullName).Any())
|
||||
{
|
||||
Is3D = true;
|
||||
}
|
||||
|
||||
if (File.Exists(Path.Combine(DirectoryRoot.FullName, "FilmIndex.xml")))
|
||||
{
|
||||
IsDBOX = true;
|
||||
}
|
||||
|
||||
//
|
||||
// Initialize file lists.
|
||||
//
|
||||
|
||||
if (DirectoryPLAYLIST != null)
|
||||
{
|
||||
FileSystemMetadata[] files = GetFiles(DirectoryPLAYLIST.FullName, ".mpls").ToArray();
|
||||
foreach (var file in files)
|
||||
{
|
||||
PlaylistFiles.Add(
|
||||
file.Name.ToUpper(), new TSPlaylistFile(this, file));
|
||||
}
|
||||
}
|
||||
|
||||
if (DirectorySTREAM != null)
|
||||
{
|
||||
FileSystemMetadata[] files = GetFiles(DirectorySTREAM.FullName, ".m2ts").ToArray();
|
||||
foreach (var file in files)
|
||||
{
|
||||
StreamFiles.Add(
|
||||
file.Name.ToUpper(), new TSStreamFile(file, _fileSystem));
|
||||
}
|
||||
}
|
||||
|
||||
if (DirectoryCLIPINF != null)
|
||||
{
|
||||
FileSystemMetadata[] files = GetFiles(DirectoryCLIPINF.FullName, ".clpi").ToArray();
|
||||
foreach (var file in files)
|
||||
{
|
||||
StreamClipFiles.Add(
|
||||
file.Name.ToUpper(), new TSStreamClipFile(file));
|
||||
}
|
||||
}
|
||||
|
||||
if (DirectorySSIF != null)
|
||||
{
|
||||
FileSystemMetadata[] files = GetFiles(DirectorySSIF.FullName, ".ssif").ToArray();
|
||||
foreach (var file in files)
|
||||
{
|
||||
InterleavedFiles.Add(
|
||||
file.Name.ToUpper(), new TSInterleavedFile(file));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<FileSystemMetadata> GetFiles(string path, string extension)
|
||||
{
|
||||
return _fileSystem.GetFiles(path, new[] { extension }, false, false);
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetFilePaths(string path, string extension)
|
||||
{
|
||||
return _fileSystem.GetFilePaths(path, new[] { extension }, false, false);
|
||||
}
|
||||
|
||||
public void Scan()
|
||||
{
|
||||
foreach (var streamClipFile in StreamClipFiles.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
streamClipFile.Scan();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (StreamClipFileScanError != null)
|
||||
{
|
||||
if (StreamClipFileScanError(streamClipFile, ex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var streamFile in StreamFiles.Values)
|
||||
{
|
||||
string ssifName = Path.GetFileNameWithoutExtension(streamFile.Name) + ".SSIF";
|
||||
if (InterleavedFiles.ContainsKey(ssifName))
|
||||
{
|
||||
streamFile.InterleavedFile = InterleavedFiles[ssifName];
|
||||
}
|
||||
}
|
||||
|
||||
TSStreamFile[] streamFiles = new TSStreamFile[StreamFiles.Count];
|
||||
StreamFiles.Values.CopyTo(streamFiles, 0);
|
||||
Array.Sort(streamFiles, CompareStreamFiles);
|
||||
|
||||
foreach (var playlistFile in PlaylistFiles.Values)
|
||||
{
|
||||
try
|
||||
{
|
||||
playlistFile.Scan(StreamFiles, StreamClipFiles);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (PlaylistFileScanError != null)
|
||||
{
|
||||
if (PlaylistFileScanError(playlistFile, ex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var streamFile in streamFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var playlists = new List<TSPlaylistFile>();
|
||||
foreach (var playlist in PlaylistFiles.Values)
|
||||
{
|
||||
foreach (var streamClip in playlist.StreamClips)
|
||||
{
|
||||
if (streamClip.Name == streamFile.Name)
|
||||
{
|
||||
playlists.Add(playlist);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
streamFile.Scan(playlists, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (StreamFileScanError != null)
|
||||
{
|
||||
if (StreamFileScanError(streamFile, ex))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var playlistFile in PlaylistFiles.Values)
|
||||
{
|
||||
playlistFile.Initialize();
|
||||
if (!Is50Hz)
|
||||
{
|
||||
foreach (var videoStream in playlistFile.VideoStreams)
|
||||
{
|
||||
if (videoStream.FrameRate == TSFrameRate.FRAMERATE_25 ||
|
||||
videoStream.FrameRate == TSFrameRate.FRAMERATE_50)
|
||||
{
|
||||
Is50Hz = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FileSystemMetadata GetDirectoryBDMV(
|
||||
string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
FileSystemMetadata dir = _fileSystem.GetDirectoryInfo(path);
|
||||
|
||||
while (dir != null)
|
||||
{
|
||||
if (string.Equals(dir.Name, "BDMV", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return dir;
|
||||
}
|
||||
var parentFolder = Path.GetDirectoryName(dir.FullName);
|
||||
if (string.IsNullOrEmpty(parentFolder))
|
||||
{
|
||||
dir = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
dir = _fileSystem.GetDirectoryInfo(parentFolder);
|
||||
}
|
||||
}
|
||||
|
||||
return GetDirectory("BDMV", _fileSystem.GetDirectoryInfo(path), 0);
|
||||
}
|
||||
|
||||
private FileSystemMetadata GetDirectory(
|
||||
string name,
|
||||
FileSystemMetadata dir,
|
||||
int searchDepth)
|
||||
{
|
||||
if (dir != null)
|
||||
{
|
||||
FileSystemMetadata[] children = _fileSystem.GetDirectories(dir.FullName).ToArray();
|
||||
foreach (var child in children)
|
||||
{
|
||||
if (string.Equals(child.Name, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return child;
|
||||
}
|
||||
}
|
||||
if (searchDepth > 0)
|
||||
{
|
||||
foreach (var child in children)
|
||||
{
|
||||
GetDirectory(
|
||||
name, child, searchDepth - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private long GetDirectorySize(FileSystemMetadata directoryInfo)
|
||||
{
|
||||
long size = 0;
|
||||
|
||||
//if (!ExcludeDirs.Contains(directoryInfo.Name.ToUpper())) // TODO: Keep?
|
||||
{
|
||||
FileSystemMetadata[] pathFiles = _fileSystem.GetFiles(directoryInfo.FullName).ToArray();
|
||||
foreach (var pathFile in pathFiles)
|
||||
{
|
||||
if (pathFile.Extension.ToUpper() == ".SSIF")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
size += pathFile.Length;
|
||||
}
|
||||
|
||||
FileSystemMetadata[] pathChildren = _fileSystem.GetDirectories(directoryInfo.FullName).ToArray();
|
||||
foreach (var pathChild in pathChildren)
|
||||
{
|
||||
size += GetDirectorySize(pathChild);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private string GetVolumeLabel(FileSystemMetadata dir)
|
||||
{
|
||||
return dir.Name;
|
||||
}
|
||||
|
||||
public int CompareStreamFiles(
|
||||
TSStreamFile x,
|
||||
TSStreamFile y)
|
||||
{
|
||||
// TODO: Use interleaved file sizes
|
||||
|
||||
if ((x == null || x.FileInfo == null) && (y == null || y.FileInfo == null))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if ((x == null || x.FileInfo == null) && (y != null && y.FileInfo != null))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if ((x != null && x.FileInfo != null) && (y == null || y.FileInfo == null))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x.FileInfo.Length > y.FileInfo.Length)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (y.FileInfo.Length > x.FileInfo.Length)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
493
BDInfo/LanguageCodes.cs
Normal file
493
BDInfo/LanguageCodes.cs
Normal file
@@ -0,0 +1,493 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class LanguageCodes
|
||||
{
|
||||
public static string GetName(string code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case "abk": return "Abkhazian";
|
||||
case "ace": return "Achinese";
|
||||
case "ach": return "Acoli";
|
||||
case "ada": return "Adangme";
|
||||
case "aar": return "Afar";
|
||||
case "afh": return "Afrihili";
|
||||
case "afr": return "Afrikaans";
|
||||
case "afa": return "Afro-Asiatic (Other)";
|
||||
case "aka": return "Akan";
|
||||
case "akk": return "Akkadian";
|
||||
case "alb": return "Albanian";
|
||||
case "sqi": return "Albanian";
|
||||
case "ale": return "Aleut";
|
||||
case "alg": return "Algonquian languages";
|
||||
case "tut": return "Altaic (Other)";
|
||||
case "amh": return "Amharic";
|
||||
case "apa": return "Apache languages";
|
||||
case "ara": return "Arabic";
|
||||
case "arc": return "Aramaic";
|
||||
case "arp": return "Arapaho";
|
||||
case "arn": return "Araucanian";
|
||||
case "arw": return "Arawak";
|
||||
case "arm": return "Armenian";
|
||||
case "hye": return "Armenian";
|
||||
case "art": return "Artificial (Other)";
|
||||
case "asm": return "Assamese";
|
||||
case "ath": return "Athapascan languages";
|
||||
case "aus": return "Australian languages";
|
||||
case "map": return "Austronesian (Other)";
|
||||
case "ava": return "Avaric";
|
||||
case "ave": return "Avestan";
|
||||
case "awa": return "Awadhi";
|
||||
case "aym": return "Aymara";
|
||||
case "aze": return "Azerbaijani";
|
||||
case "ban": return "Balinese";
|
||||
case "bat": return "Baltic (Other)";
|
||||
case "bal": return "Baluchi";
|
||||
case "bam": return "Bambara";
|
||||
case "bai": return "Bamileke languages";
|
||||
case "bad": return "Banda";
|
||||
case "bnt": return "Bantu (Other)";
|
||||
case "bas": return "Basa";
|
||||
case "bak": return "Bashkir";
|
||||
case "baq": return "Basque";
|
||||
case "eus": return "Basque";
|
||||
case "btk": return "Batak (Indonesia)";
|
||||
case "bej": return "Beja";
|
||||
case "bel": return "Belarusian";
|
||||
case "bem": return "Bemba";
|
||||
case "ben": return "Bengali";
|
||||
case "ber": return "Berber (Other)";
|
||||
case "bho": return "Bhojpuri";
|
||||
case "bih": return "Bihari";
|
||||
case "bik": return "Bikol";
|
||||
case "bin": return "Bini";
|
||||
case "bis": return "Bislama";
|
||||
case "bos": return "Bosnian";
|
||||
case "bra": return "Braj";
|
||||
case "bre": return "Breton";
|
||||
case "bug": return "Buginese";
|
||||
case "bul": return "Bulgarian";
|
||||
case "bua": return "Buriat";
|
||||
case "bur": return "Burmese";
|
||||
case "mya": return "Burmese";
|
||||
case "cad": return "Caddo";
|
||||
case "car": return "Carib";
|
||||
case "cat": return "Catalan";
|
||||
case "cau": return "Caucasian (Other)";
|
||||
case "ceb": return "Cebuano";
|
||||
case "cel": return "Celtic (Other)";
|
||||
case "cai": return "Central American Indian (Other)";
|
||||
case "chg": return "Chagatai";
|
||||
case "cmc": return "Chamic languages";
|
||||
case "cha": return "Chamorro";
|
||||
case "che": return "Chechen";
|
||||
case "chr": return "Cherokee";
|
||||
case "chy": return "Cheyenne";
|
||||
case "chb": return "Chibcha";
|
||||
case "chi": return "Chinese";
|
||||
case "zho": return "Chinese";
|
||||
case "chn": return "Chinook jargon";
|
||||
case "chp": return "Chipewyan";
|
||||
case "cho": return "Choctaw";
|
||||
case "chu": return "Church Slavic";
|
||||
case "chk": return "Chuukese";
|
||||
case "chv": return "Chuvash";
|
||||
case "cop": return "Coptic";
|
||||
case "cor": return "Cornish";
|
||||
case "cos": return "Corsican";
|
||||
case "cre": return "Cree";
|
||||
case "mus": return "Creek";
|
||||
case "crp": return "Creoles and pidgins (Other)";
|
||||
case "cpe": return "Creoles and pidgins,";
|
||||
case "cpf": return "Creoles and pidgins,";
|
||||
case "cpp": return "Creoles and pidgins,";
|
||||
case "scr": return "Croatian";
|
||||
case "hrv": return "Croatian";
|
||||
case "cus": return "Cushitic (Other)";
|
||||
case "cze": return "Czech";
|
||||
case "ces": return "Czech";
|
||||
case "dak": return "Dakota";
|
||||
case "dan": return "Danish";
|
||||
case "day": return "Dayak";
|
||||
case "del": return "Delaware";
|
||||
case "din": return "Dinka";
|
||||
case "div": return "Divehi";
|
||||
case "doi": return "Dogri";
|
||||
case "dgr": return "Dogrib";
|
||||
case "dra": return "Dravidian (Other)";
|
||||
case "dua": return "Duala";
|
||||
case "dut": return "Dutch";
|
||||
case "nld": return "Dutch";
|
||||
case "dum": return "Dutch, Middle (ca. 1050-1350)";
|
||||
case "dyu": return "Dyula";
|
||||
case "dzo": return "Dzongkha";
|
||||
case "efi": return "Efik";
|
||||
case "egy": return "Egyptian (Ancient)";
|
||||
case "eka": return "Ekajuk";
|
||||
case "elx": return "Elamite";
|
||||
case "eng": return "English";
|
||||
case "enm": return "English, Middle (1100-1500)";
|
||||
case "ang": return "English, Old (ca.450-1100)";
|
||||
case "epo": return "Esperanto";
|
||||
case "est": return "Estonian";
|
||||
case "ewe": return "Ewe";
|
||||
case "ewo": return "Ewondo";
|
||||
case "fan": return "Fang";
|
||||
case "fat": return "Fanti";
|
||||
case "fao": return "Faroese";
|
||||
case "fij": return "Fijian";
|
||||
case "fin": return "Finnish";
|
||||
case "fiu": return "Finno-Ugrian (Other)";
|
||||
case "fon": return "Fon";
|
||||
case "fre": return "French";
|
||||
case "fra": return "French";
|
||||
case "frm": return "French, Middle (ca.1400-1600)";
|
||||
case "fro": return "French, Old (842-ca.1400)";
|
||||
case "fry": return "Frisian";
|
||||
case "fur": return "Friulian";
|
||||
case "ful": return "Fulah";
|
||||
case "gaa": return "Ga";
|
||||
case "glg": return "Gallegan";
|
||||
case "lug": return "Ganda";
|
||||
case "gay": return "Gayo";
|
||||
case "gba": return "Gbaya";
|
||||
case "gez": return "Geez";
|
||||
case "geo": return "Georgian";
|
||||
case "kat": return "Georgian";
|
||||
case "ger": return "German";
|
||||
case "deu": return "German";
|
||||
case "nds": return "Saxon";
|
||||
case "gmh": return "German, Middle High (ca.1050-1500)";
|
||||
case "goh": return "German, Old High (ca.750-1050)";
|
||||
case "gem": return "Germanic (Other)";
|
||||
case "gil": return "Gilbertese";
|
||||
case "gon": return "Gondi";
|
||||
case "gor": return "Gorontalo";
|
||||
case "got": return "Gothic";
|
||||
case "grb": return "Grebo";
|
||||
case "grc": return "Greek, Ancient (to 1453)";
|
||||
case "gre": return "Greek";
|
||||
case "ell": return "Greek";
|
||||
case "grn": return "Guarani";
|
||||
case "guj": return "Gujarati";
|
||||
case "gwi": return "Gwich´in";
|
||||
case "hai": return "Haida";
|
||||
case "hau": return "Hausa";
|
||||
case "haw": return "Hawaiian";
|
||||
case "heb": return "Hebrew";
|
||||
case "her": return "Herero";
|
||||
case "hil": return "Hiligaynon";
|
||||
case "him": return "Himachali";
|
||||
case "hin": return "Hindi";
|
||||
case "hmo": return "Hiri Motu";
|
||||
case "hit": return "Hittite";
|
||||
case "hmn": return "Hmong";
|
||||
case "hun": return "Hungarian";
|
||||
case "hup": return "Hupa";
|
||||
case "iba": return "Iban";
|
||||
case "ice": return "Icelandic";
|
||||
case "isl": return "Icelandic";
|
||||
case "ibo": return "Igbo";
|
||||
case "ijo": return "Ijo";
|
||||
case "ilo": return "Iloko";
|
||||
case "inc": return "Indic (Other)";
|
||||
case "ine": return "Indo-European (Other)";
|
||||
case "ind": return "Indonesian";
|
||||
case "ina": return "Interlingua (International";
|
||||
case "ile": return "Interlingue";
|
||||
case "iku": return "Inuktitut";
|
||||
case "ipk": return "Inupiaq";
|
||||
case "ira": return "Iranian (Other)";
|
||||
case "gle": return "Irish";
|
||||
case "mga": return "Irish, Middle (900-1200)";
|
||||
case "sga": return "Irish, Old (to 900)";
|
||||
case "iro": return "Iroquoian languages";
|
||||
case "ita": return "Italian";
|
||||
case "jpn": return "Japanese";
|
||||
case "jav": return "Javanese";
|
||||
case "jrb": return "Judeo-Arabic";
|
||||
case "jpr": return "Judeo-Persian";
|
||||
case "kab": return "Kabyle";
|
||||
case "kac": return "Kachin";
|
||||
case "kal": return "Kalaallisut";
|
||||
case "kam": return "Kamba";
|
||||
case "kan": return "Kannada";
|
||||
case "kau": return "Kanuri";
|
||||
case "kaa": return "Kara-Kalpak";
|
||||
case "kar": return "Karen";
|
||||
case "kas": return "Kashmiri";
|
||||
case "kaw": return "Kawi";
|
||||
case "kaz": return "Kazakh";
|
||||
case "kha": return "Khasi";
|
||||
case "khm": return "Khmer";
|
||||
case "khi": return "Khoisan (Other)";
|
||||
case "kho": return "Khotanese";
|
||||
case "kik": return "Kikuyu";
|
||||
case "kmb": return "Kimbundu";
|
||||
case "kin": return "Kinyarwanda";
|
||||
case "kir": return "Kirghiz";
|
||||
case "kom": return "Komi";
|
||||
case "kon": return "Kongo";
|
||||
case "kok": return "Konkani";
|
||||
case "kor": return "Korean";
|
||||
case "kos": return "Kosraean";
|
||||
case "kpe": return "Kpelle";
|
||||
case "kro": return "Kru";
|
||||
case "kua": return "Kuanyama";
|
||||
case "kum": return "Kumyk";
|
||||
case "kur": return "Kurdish";
|
||||
case "kru": return "Kurukh";
|
||||
case "kut": return "Kutenai";
|
||||
case "lad": return "Ladino";
|
||||
case "lah": return "Lahnda";
|
||||
case "lam": return "Lamba";
|
||||
case "lao": return "Lao";
|
||||
case "lat": return "Latin";
|
||||
case "lav": return "Latvian";
|
||||
case "ltz": return "Letzeburgesch";
|
||||
case "lez": return "Lezghian";
|
||||
case "lin": return "Lingala";
|
||||
case "lit": return "Lithuanian";
|
||||
case "loz": return "Lozi";
|
||||
case "lub": return "Luba-Katanga";
|
||||
case "lua": return "Luba-Lulua";
|
||||
case "lui": return "Luiseno";
|
||||
case "lun": return "Lunda";
|
||||
case "luo": return "Luo (Kenya and Tanzania)";
|
||||
case "lus": return "Lushai";
|
||||
case "mac": return "Macedonian";
|
||||
case "mkd": return "Macedonian";
|
||||
case "mad": return "Madurese";
|
||||
case "mag": return "Magahi";
|
||||
case "mai": return "Maithili";
|
||||
case "mak": return "Makasar";
|
||||
case "mlg": return "Malagasy";
|
||||
case "may": return "Malay";
|
||||
case "msa": return "Malay";
|
||||
case "mal": return "Malayalam";
|
||||
case "mlt": return "Maltese";
|
||||
case "mnc": return "Manchu";
|
||||
case "mdr": return "Mandar";
|
||||
case "man": return "Mandingo";
|
||||
case "mni": return "Manipuri";
|
||||
case "mno": return "Manobo languages";
|
||||
case "glv": return "Manx";
|
||||
case "mao": return "Maori";
|
||||
case "mri": return "Maori";
|
||||
case "mar": return "Marathi";
|
||||
case "chm": return "Mari";
|
||||
case "mah": return "Marshall";
|
||||
case "mwr": return "Marwari";
|
||||
case "mas": return "Masai";
|
||||
case "myn": return "Mayan languages";
|
||||
case "men": return "Mende";
|
||||
case "mic": return "Micmac";
|
||||
case "min": return "Minangkabau";
|
||||
case "mis": return "Miscellaneous languages";
|
||||
case "moh": return "Mohawk";
|
||||
case "mol": return "Moldavian";
|
||||
case "mkh": return "Mon-Khmer (Other)";
|
||||
case "lol": return "Mongo";
|
||||
case "mon": return "Mongolian";
|
||||
case "mos": return "Mossi";
|
||||
case "mul": return "Multiple languages";
|
||||
case "mun": return "Munda languages";
|
||||
case "nah": return "Nahuatl";
|
||||
case "nau": return "Nauru";
|
||||
case "nav": return "Navajo";
|
||||
case "nde": return "Ndebele, North";
|
||||
case "nbl": return "Ndebele, South";
|
||||
case "ndo": return "Ndonga";
|
||||
case "nep": return "Nepali";
|
||||
case "new": return "Newari";
|
||||
case "nia": return "Nias";
|
||||
case "nic": return "Niger-Kordofanian (Other)";
|
||||
case "ssa": return "Nilo-Saharan (Other)";
|
||||
case "niu": return "Niuean";
|
||||
case "non": return "Norse, Old";
|
||||
case "nai": return "North American Indian (Other)";
|
||||
case "sme": return "Northern Sami";
|
||||
case "nor": return "Norwegian";
|
||||
case "nob": return "Norwegian Bokmål";
|
||||
case "nno": return "Norwegian Nynorsk";
|
||||
case "nub": return "Nubian languages";
|
||||
case "nym": return "Nyamwezi";
|
||||
case "nya": return "Nyanja";
|
||||
case "nyn": return "Nyankole";
|
||||
case "nyo": return "Nyoro";
|
||||
case "nzi": return "Nzima";
|
||||
case "oci": return "Occitan";
|
||||
case "oji": return "Ojibwa";
|
||||
case "ori": return "Oriya";
|
||||
case "orm": return "Oromo";
|
||||
case "osa": return "Osage";
|
||||
case "oss": return "Ossetian";
|
||||
case "oto": return "Otomian languages";
|
||||
case "pal": return "Pahlavi";
|
||||
case "pau": return "Palauan";
|
||||
case "pli": return "Pali";
|
||||
case "pam": return "Pampanga";
|
||||
case "pag": return "Pangasinan";
|
||||
case "pan": return "Panjabi";
|
||||
case "pap": return "Papiamento";
|
||||
case "paa": return "Papuan (Other)";
|
||||
case "per": return "Persian";
|
||||
case "fas": return "Persian";
|
||||
case "peo": return "Persian, Old (ca.600-400 B.C.)";
|
||||
case "phi": return "Philippine (Other)";
|
||||
case "phn": return "Phoenician";
|
||||
case "pon": return "Pohnpeian";
|
||||
case "pol": return "Polish";
|
||||
case "por": return "Portuguese";
|
||||
case "pra": return "Prakrit languages";
|
||||
case "pro": return "Provençal";
|
||||
case "pus": return "Pushto";
|
||||
case "que": return "Quechua";
|
||||
case "roh": return "Raeto-Romance";
|
||||
case "raj": return "Rajasthani";
|
||||
case "rap": return "Rapanui";
|
||||
case "rar": return "Rarotongan";
|
||||
case "roa": return "Romance (Other)";
|
||||
case "rum": return "Romanian";
|
||||
case "ron": return "Romanian";
|
||||
case "rom": return "Romany";
|
||||
case "run": return "Rundi";
|
||||
case "rus": return "Russian";
|
||||
case "sal": return "Salishan languages";
|
||||
case "sam": return "Samaritan Aramaic";
|
||||
case "smi": return "Sami languages (Other)";
|
||||
case "smo": return "Samoan";
|
||||
case "sad": return "Sandawe";
|
||||
case "sag": return "Sango";
|
||||
case "san": return "Sanskrit";
|
||||
case "sat": return "Santali";
|
||||
case "srd": return "Sardinian";
|
||||
case "sas": return "Sasak";
|
||||
case "sco": return "Scots";
|
||||
case "gla": return "Gaelic";
|
||||
case "sel": return "Selkup";
|
||||
case "sem": return "Semitic (Other)";
|
||||
case "scc": return "Serbian";
|
||||
case "srp": return "Serbian";
|
||||
case "srr": return "Serer";
|
||||
case "shn": return "Shan";
|
||||
case "sna": return "Shona";
|
||||
case "sid": return "Sidamo";
|
||||
case "sgn": return "Sign languages";
|
||||
case "bla": return "Siksika";
|
||||
case "snd": return "Sindhi";
|
||||
case "sin": return "Sinhalese";
|
||||
case "sit": return "Sino-Tibetan (Other)";
|
||||
case "sio": return "Siouan languages";
|
||||
case "den": return "Slave (Athapascan)";
|
||||
case "sla": return "Slavic (Other)";
|
||||
case "slo": return "Slovak";
|
||||
case "slk": return "Slovak";
|
||||
case "slv": return "Slovenian";
|
||||
case "sog": return "Sogdian";
|
||||
case "som": return "Somali";
|
||||
case "son": return "Songhai";
|
||||
case "snk": return "Soninke";
|
||||
case "wen": return "Sorbian languages";
|
||||
case "nso": return "Sotho, Northern";
|
||||
case "sot": return "Sotho, Southern";
|
||||
case "sai": return "South American Indian (Other)";
|
||||
case "spa": return "Spanish";
|
||||
case "suk": return "Sukuma";
|
||||
case "sux": return "Sumerian";
|
||||
case "sun": return "Sundanese";
|
||||
case "sus": return "Susu";
|
||||
case "swa": return "Swahili";
|
||||
case "ssw": return "Swati";
|
||||
case "swe": return "Swedish";
|
||||
case "syr": return "Syriac";
|
||||
case "tgl": return "Tagalog";
|
||||
case "tah": return "Tahitian";
|
||||
case "tai": return "Tai (Other)";
|
||||
case "tgk": return "Tajik";
|
||||
case "tmh": return "Tamashek";
|
||||
case "tam": return "Tamil";
|
||||
case "tat": return "Tatar";
|
||||
case "tel": return "Telugu";
|
||||
case "ter": return "Tereno";
|
||||
case "tet": return "Tetum";
|
||||
case "tha": return "Thai";
|
||||
case "tib": return "Tibetan";
|
||||
case "bod": return "Tibetan";
|
||||
case "tig": return "Tigre";
|
||||
case "tir": return "Tigrinya";
|
||||
case "tem": return "Timne";
|
||||
case "tiv": return "Tiv";
|
||||
case "tli": return "Tlingit";
|
||||
case "tpi": return "Tok Pisin";
|
||||
case "tkl": return "Tokelau";
|
||||
case "tog": return "Tonga (Nyasa)";
|
||||
case "ton": return "Tonga (Tonga Islands)";
|
||||
case "tsi": return "Tsimshian";
|
||||
case "tso": return "Tsonga";
|
||||
case "tsn": return "Tswana";
|
||||
case "tum": return "Tumbuka";
|
||||
case "tur": return "Turkish";
|
||||
case "ota": return "Turkish, Ottoman (1500-1928)";
|
||||
case "tuk": return "Turkmen";
|
||||
case "tvl": return "Tuvalu";
|
||||
case "tyv": return "Tuvinian";
|
||||
case "twi": return "Twi";
|
||||
case "uga": return "Ugaritic";
|
||||
case "uig": return "Uighur";
|
||||
case "ukr": return "Ukrainian";
|
||||
case "umb": return "Umbundu";
|
||||
case "und": return "Undetermined";
|
||||
case "urd": return "Urdu";
|
||||
case "uzb": return "Uzbek";
|
||||
case "vai": return "Vai";
|
||||
case "ven": return "Venda";
|
||||
case "vie": return "Vietnamese";
|
||||
case "vol": return "Volapük";
|
||||
case "vot": return "Votic";
|
||||
case "wak": return "Wakashan languages";
|
||||
case "wal": return "Walamo";
|
||||
case "war": return "Waray";
|
||||
case "was": return "Washo";
|
||||
case "wel": return "Welsh";
|
||||
case "cym": return "Welsh";
|
||||
case "wol": return "Wolof";
|
||||
case "xho": return "Xhosa";
|
||||
case "sah": return "Yakut";
|
||||
case "yao": return "Yao";
|
||||
case "yap": return "Yapese";
|
||||
case "yid": return "Yiddish";
|
||||
case "yor": return "Yoruba";
|
||||
case "ypk": return "Yupik languages";
|
||||
case "znd": return "Zande";
|
||||
case "zap": return "Zapotec";
|
||||
case "zen": return "Zenaga";
|
||||
case "zha": return "Zhuang";
|
||||
case "zul": return "Zulu";
|
||||
case "zun": return "Zuni";
|
||||
|
||||
default: return code;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
BDInfo/Properties/AssemblyInfo.cs
Normal file
21
BDInfo/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("BDInfo")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016 CinemaSquid. Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
5
BDInfo/ReadMe.txt
Normal file
5
BDInfo/ReadMe.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
The source is taken from the BDRom folder of this project:
|
||||
|
||||
http://www.cinemasquid.com/blu-ray/tools/bdinfo
|
||||
|
||||
BDInfoSettings was taken from the FormSettings class, and changed so that the settings all return defaults.
|
||||
309
BDInfo/TSCodecAC3.cs
Normal file
309
BDInfo/TSCodecAC3.cs
Normal file
@@ -0,0 +1,309 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
#undef DEBUG
|
||||
using System.IO;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecAC3
|
||||
{
|
||||
private static byte[] eac3_blocks = new byte[] { 1, 2, 3, 6 };
|
||||
|
||||
public static void Scan(
|
||||
TSAudioStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
if (stream.IsInitialized) return;
|
||||
|
||||
byte[] sync = buffer.ReadBytes(2);
|
||||
if (sync == null ||
|
||||
sync[0] != 0x0B ||
|
||||
sync[1] != 0x77)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int sr_code = 0;
|
||||
int frame_size = 0;
|
||||
int frame_size_code = 0;
|
||||
int channel_mode = 0;
|
||||
int lfe_on = 0;
|
||||
int dial_norm = 0;
|
||||
int num_blocks = 0;
|
||||
|
||||
byte[] hdr = buffer.ReadBytes(4);
|
||||
int bsid = (hdr[3] & 0xF8) >> 3;
|
||||
buffer.Seek(-4, SeekOrigin.Current);
|
||||
if (bsid <= 10)
|
||||
{
|
||||
byte[] crc = buffer.ReadBytes(2);
|
||||
sr_code = buffer.ReadBits(2);
|
||||
frame_size_code = buffer.ReadBits(6);
|
||||
bsid = buffer.ReadBits(5);
|
||||
int bsmod = buffer.ReadBits(3);
|
||||
|
||||
channel_mode = buffer.ReadBits(3);
|
||||
int cmixlev = 0;
|
||||
if (((channel_mode & 0x1) > 0) && (channel_mode != 0x1))
|
||||
{
|
||||
cmixlev = buffer.ReadBits(2);
|
||||
}
|
||||
int surmixlev = 0;
|
||||
if ((channel_mode & 0x4) > 0)
|
||||
{
|
||||
surmixlev = buffer.ReadBits(2);
|
||||
}
|
||||
int dsurmod = 0;
|
||||
if (channel_mode == 0x2)
|
||||
{
|
||||
dsurmod = buffer.ReadBits(2);
|
||||
if (dsurmod == 0x2)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.Surround;
|
||||
}
|
||||
}
|
||||
lfe_on = buffer.ReadBits(1);
|
||||
dial_norm = buffer.ReadBits(5);
|
||||
int compr = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
compr = buffer.ReadBits(8);
|
||||
}
|
||||
int langcod = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
langcod = buffer.ReadBits(8);
|
||||
}
|
||||
int mixlevel = 0;
|
||||
int roomtyp = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
mixlevel = buffer.ReadBits(5);
|
||||
roomtyp = buffer.ReadBits(2);
|
||||
}
|
||||
if (channel_mode == 0)
|
||||
{
|
||||
int dialnorm2 = buffer.ReadBits(5);
|
||||
int compr2 = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
compr2 = buffer.ReadBits(8);
|
||||
}
|
||||
int langcod2 = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
langcod2 = buffer.ReadBits(8);
|
||||
}
|
||||
int mixlevel2 = 0;
|
||||
int roomtyp2 = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
mixlevel2 = buffer.ReadBits(5);
|
||||
roomtyp2 = buffer.ReadBits(2);
|
||||
}
|
||||
}
|
||||
int copyrightb = buffer.ReadBits(1);
|
||||
int origbs = buffer.ReadBits(1);
|
||||
if (bsid == 6)
|
||||
{
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
int dmixmod = buffer.ReadBits(2);
|
||||
int ltrtcmixlev = buffer.ReadBits(3);
|
||||
int ltrtsurmixlev = buffer.ReadBits(3);
|
||||
int lorocmixlev = buffer.ReadBits(3);
|
||||
int lorosurmixlev = buffer.ReadBits(3);
|
||||
}
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
int dsurexmod = buffer.ReadBits(2);
|
||||
int dheadphonmod = buffer.ReadBits(2);
|
||||
if (dheadphonmod == 0x2)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
int adconvtyp = buffer.ReadBits(1);
|
||||
int xbsi2 = buffer.ReadBits(8);
|
||||
int encinfo = buffer.ReadBits(1);
|
||||
if (dsurexmod == 2)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.Extended;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int frame_type = buffer.ReadBits(2);
|
||||
int substreamid = buffer.ReadBits(3);
|
||||
frame_size = (buffer.ReadBits(11) + 1) << 1;
|
||||
|
||||
sr_code = buffer.ReadBits(2);
|
||||
if (sr_code == 3)
|
||||
{
|
||||
sr_code = buffer.ReadBits(2);
|
||||
}
|
||||
else
|
||||
{
|
||||
num_blocks = buffer.ReadBits(2);
|
||||
}
|
||||
channel_mode = buffer.ReadBits(3);
|
||||
lfe_on = buffer.ReadBits(1);
|
||||
}
|
||||
|
||||
switch (channel_mode)
|
||||
{
|
||||
case 0: // 1+1
|
||||
stream.ChannelCount = 2;
|
||||
if (stream.AudioMode == TSAudioMode.Unknown)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.DualMono;
|
||||
}
|
||||
break;
|
||||
case 1: // 1/0
|
||||
stream.ChannelCount = 1;
|
||||
break;
|
||||
case 2: // 2/0
|
||||
stream.ChannelCount = 2;
|
||||
if (stream.AudioMode == TSAudioMode.Unknown)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.Stereo;
|
||||
}
|
||||
break;
|
||||
case 3: // 3/0
|
||||
stream.ChannelCount = 3;
|
||||
break;
|
||||
case 4: // 2/1
|
||||
stream.ChannelCount = 3;
|
||||
break;
|
||||
case 5: // 3/1
|
||||
stream.ChannelCount = 4;
|
||||
break;
|
||||
case 6: // 2/2
|
||||
stream.ChannelCount = 4;
|
||||
break;
|
||||
case 7: // 3/2
|
||||
stream.ChannelCount = 5;
|
||||
break;
|
||||
default:
|
||||
stream.ChannelCount = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sr_code)
|
||||
{
|
||||
case 0:
|
||||
stream.SampleRate = 48000;
|
||||
break;
|
||||
case 1:
|
||||
stream.SampleRate = 44100;
|
||||
break;
|
||||
case 2:
|
||||
stream.SampleRate = 32000;
|
||||
break;
|
||||
default:
|
||||
stream.SampleRate = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (bsid <= 10)
|
||||
{
|
||||
switch (frame_size_code >> 1)
|
||||
{
|
||||
case 18:
|
||||
stream.BitRate = 640000;
|
||||
break;
|
||||
case 17:
|
||||
stream.BitRate = 576000;
|
||||
break;
|
||||
case 16:
|
||||
stream.BitRate = 512000;
|
||||
break;
|
||||
case 15:
|
||||
stream.BitRate = 448000;
|
||||
break;
|
||||
case 14:
|
||||
stream.BitRate = 384000;
|
||||
break;
|
||||
case 13:
|
||||
stream.BitRate = 320000;
|
||||
break;
|
||||
case 12:
|
||||
stream.BitRate = 256000;
|
||||
break;
|
||||
case 11:
|
||||
stream.BitRate = 224000;
|
||||
break;
|
||||
case 10:
|
||||
stream.BitRate = 192000;
|
||||
break;
|
||||
case 9:
|
||||
stream.BitRate = 160000;
|
||||
break;
|
||||
case 8:
|
||||
stream.BitRate = 128000;
|
||||
break;
|
||||
case 7:
|
||||
stream.BitRate = 112000;
|
||||
break;
|
||||
case 6:
|
||||
stream.BitRate = 96000;
|
||||
break;
|
||||
case 5:
|
||||
stream.BitRate = 80000;
|
||||
break;
|
||||
case 4:
|
||||
stream.BitRate = 64000;
|
||||
break;
|
||||
case 3:
|
||||
stream.BitRate = 56000;
|
||||
break;
|
||||
case 2:
|
||||
stream.BitRate = 48000;
|
||||
break;
|
||||
case 1:
|
||||
stream.BitRate = 40000;
|
||||
break;
|
||||
case 0:
|
||||
stream.BitRate = 32000;
|
||||
break;
|
||||
default:
|
||||
stream.BitRate = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.BitRate = (long)
|
||||
(4.0 * frame_size * stream.SampleRate / (num_blocks * 256));
|
||||
}
|
||||
|
||||
stream.LFE = lfe_on;
|
||||
if (stream.StreamType != TSStreamType.AC3_PLUS_AUDIO &&
|
||||
stream.StreamType != TSStreamType.AC3_PLUS_SECONDARY_AUDIO)
|
||||
{
|
||||
stream.DialNorm = dial_norm - 31;
|
||||
}
|
||||
stream.IsVBR = false;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
BDInfo/TSCodecAVC.cs
Normal file
148
BDInfo/TSCodecAVC.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecAVC
|
||||
{
|
||||
public static void Scan(
|
||||
TSVideoStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
uint parse = 0;
|
||||
byte accessUnitDelimiterParse = 0;
|
||||
byte sequenceParameterSetParse = 0;
|
||||
string profile = null;
|
||||
string level = null;
|
||||
byte constraintSet0Flag = 0;
|
||||
byte constraintSet1Flag = 0;
|
||||
byte constraintSet2Flag = 0;
|
||||
byte constraintSet3Flag = 0;
|
||||
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
parse = (parse << 8) + buffer.ReadByte();
|
||||
|
||||
if (parse == 0x00000109)
|
||||
{
|
||||
accessUnitDelimiterParse = 1;
|
||||
}
|
||||
else if (accessUnitDelimiterParse > 0)
|
||||
{
|
||||
--accessUnitDelimiterParse;
|
||||
if (accessUnitDelimiterParse == 0)
|
||||
{
|
||||
switch ((parse & 0xFF) >> 5)
|
||||
{
|
||||
case 0: // I
|
||||
case 3: // SI
|
||||
case 5: // I, SI
|
||||
tag = "I";
|
||||
break;
|
||||
|
||||
case 1: // I, P
|
||||
case 4: // SI, SP
|
||||
case 6: // I, SI, P, SP
|
||||
tag = "P";
|
||||
break;
|
||||
|
||||
case 2: // I, P, B
|
||||
case 7: // I, SI, P, SP, B
|
||||
tag = "B";
|
||||
break;
|
||||
}
|
||||
if (stream.IsInitialized) return;
|
||||
}
|
||||
}
|
||||
else if (parse == 0x00000127 || parse == 0x00000167)
|
||||
{
|
||||
sequenceParameterSetParse = 3;
|
||||
}
|
||||
else if (sequenceParameterSetParse > 0)
|
||||
{
|
||||
--sequenceParameterSetParse;
|
||||
switch (sequenceParameterSetParse)
|
||||
{
|
||||
case 2:
|
||||
switch (parse & 0xFF)
|
||||
{
|
||||
case 66:
|
||||
profile = "Baseline Profile";
|
||||
break;
|
||||
case 77:
|
||||
profile = "Main Profile";
|
||||
break;
|
||||
case 88:
|
||||
profile = "Extended Profile";
|
||||
break;
|
||||
case 100:
|
||||
profile = "High Profile";
|
||||
break;
|
||||
case 110:
|
||||
profile = "High 10 Profile";
|
||||
break;
|
||||
case 122:
|
||||
profile = "High 4:2:2 Profile";
|
||||
break;
|
||||
case 144:
|
||||
profile = "High 4:4:4 Profile";
|
||||
break;
|
||||
default:
|
||||
profile = "Unknown Profile";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
constraintSet0Flag = (byte)
|
||||
((parse & 0x80) >> 7);
|
||||
constraintSet1Flag = (byte)
|
||||
((parse & 0x40) >> 6);
|
||||
constraintSet2Flag = (byte)
|
||||
((parse & 0x20) >> 5);
|
||||
constraintSet3Flag = (byte)
|
||||
((parse & 0x10) >> 4);
|
||||
break;
|
||||
|
||||
case 0:
|
||||
byte b = (byte)(parse & 0xFF);
|
||||
if (b == 11 && constraintSet3Flag == 1)
|
||||
{
|
||||
level = "1b";
|
||||
}
|
||||
else
|
||||
{
|
||||
level = string.Format(
|
||||
"{0:D}.{1:D}",
|
||||
b / 10, (b - ((b / 10) * 10)));
|
||||
}
|
||||
stream.EncodingProfile = string.Format(
|
||||
"{0} {1}", profile, level);
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
159
BDInfo/TSCodecDTS.cs
Normal file
159
BDInfo/TSCodecDTS.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecDTS
|
||||
{
|
||||
private static int[] dca_sample_rates =
|
||||
{
|
||||
0, 8000, 16000, 32000, 0, 0, 11025, 22050, 44100, 0, 0,
|
||||
12000, 24000, 48000, 96000, 192000
|
||||
};
|
||||
|
||||
private static int[] dca_bit_rates =
|
||||
{
|
||||
32000, 56000, 64000, 96000, 112000, 128000,
|
||||
192000, 224000, 256000, 320000, 384000,
|
||||
448000, 512000, 576000, 640000, 768000,
|
||||
896000, 1024000, 1152000, 1280000, 1344000,
|
||||
1408000, 1411200, 1472000, 1509000, 1920000,
|
||||
2048000, 3072000, 3840000, 1/*open*/, 2/*variable*/, 3/*lossless*/
|
||||
};
|
||||
|
||||
private static int[] dca_channels =
|
||||
{
|
||||
1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 6, 6, 6, 7, 8, 8
|
||||
};
|
||||
|
||||
private static int[] dca_bits_per_sample =
|
||||
{
|
||||
16, 16, 20, 20, 0, 24, 24
|
||||
};
|
||||
|
||||
public static void Scan(
|
||||
TSAudioStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
long bitrate,
|
||||
ref string tag)
|
||||
{
|
||||
if (stream.IsInitialized) return;
|
||||
|
||||
bool syncFound = false;
|
||||
uint sync = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
sync = (sync << 8) + buffer.ReadByte();
|
||||
if (sync == 0x7FFE8001)
|
||||
{
|
||||
syncFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!syncFound) return;
|
||||
|
||||
int frame_type = buffer.ReadBits(1);
|
||||
int samples_deficit = buffer.ReadBits(5);
|
||||
int crc_present = buffer.ReadBits(1);
|
||||
int sample_blocks = buffer.ReadBits(7);
|
||||
int frame_size = buffer.ReadBits(14);
|
||||
if (frame_size < 95)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int amode = buffer.ReadBits(6);
|
||||
int sample_rate = buffer.ReadBits(4);
|
||||
if (sample_rate < 0 || sample_rate >= dca_sample_rates.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int bit_rate = buffer.ReadBits(5);
|
||||
if (bit_rate < 0 || bit_rate >= dca_bit_rates.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int downmix = buffer.ReadBits(1);
|
||||
int dynrange = buffer.ReadBits(1);
|
||||
int timestamp = buffer.ReadBits(1);
|
||||
int aux_data = buffer.ReadBits(1);
|
||||
int hdcd = buffer.ReadBits(1);
|
||||
int ext_descr = buffer.ReadBits(3);
|
||||
int ext_coding = buffer.ReadBits(1);
|
||||
int aspf = buffer.ReadBits(1);
|
||||
int lfe = buffer.ReadBits(2);
|
||||
int predictor_history = buffer.ReadBits(1);
|
||||
if (crc_present == 1)
|
||||
{
|
||||
int crc = buffer.ReadBits(16);
|
||||
}
|
||||
int multirate_inter = buffer.ReadBits(1);
|
||||
int version = buffer.ReadBits(4);
|
||||
int copy_history = buffer.ReadBits(2);
|
||||
int source_pcm_res = buffer.ReadBits(3);
|
||||
int front_sum = buffer.ReadBits(1);
|
||||
int surround_sum = buffer.ReadBits(1);
|
||||
int dialog_norm = buffer.ReadBits(4);
|
||||
if (source_pcm_res < 0 || source_pcm_res >= dca_bits_per_sample.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int subframes = buffer.ReadBits(4);
|
||||
int total_channels = buffer.ReadBits(3) + 1 + ext_coding;
|
||||
|
||||
stream.SampleRate = dca_sample_rates[sample_rate];
|
||||
stream.ChannelCount = total_channels;
|
||||
stream.LFE = (lfe > 0 ? 1 : 0);
|
||||
stream.BitDepth = dca_bits_per_sample[source_pcm_res];
|
||||
stream.DialNorm = -dialog_norm;
|
||||
if ((source_pcm_res & 0x1) == 0x1)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.Extended;
|
||||
}
|
||||
|
||||
stream.BitRate = (uint)dca_bit_rates[bit_rate];
|
||||
switch (stream.BitRate)
|
||||
{
|
||||
case 1:
|
||||
if (bitrate > 0)
|
||||
{
|
||||
stream.BitRate = bitrate;
|
||||
stream.IsVBR = false;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.BitRate = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
stream.IsVBR = false;
|
||||
stream.IsInitialized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
246
BDInfo/TSCodecDTSHD.cs
Normal file
246
BDInfo/TSCodecDTSHD.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecDTSHD
|
||||
{
|
||||
private static int[] SampleRates = new int[]
|
||||
{ 0x1F40, 0x3E80, 0x7D00, 0x0FA00, 0x1F400, 0x5622, 0x0AC44, 0x15888, 0x2B110, 0x56220, 0x2EE0, 0x5DC0, 0x0BB80, 0x17700, 0x2EE00, 0x5DC00 };
|
||||
|
||||
public static void Scan(
|
||||
TSAudioStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
long bitrate,
|
||||
ref string tag)
|
||||
{
|
||||
if (stream.IsInitialized &&
|
||||
(stream.StreamType == TSStreamType.DTS_HD_SECONDARY_AUDIO ||
|
||||
(stream.CoreStream != null &&
|
||||
stream.CoreStream.IsInitialized))) return;
|
||||
|
||||
bool syncFound = false;
|
||||
uint sync = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
sync = (sync << 8) + buffer.ReadByte();
|
||||
if (sync == 0x64582025)
|
||||
{
|
||||
syncFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!syncFound)
|
||||
{
|
||||
tag = "CORE";
|
||||
if (stream.CoreStream == null)
|
||||
{
|
||||
stream.CoreStream = new TSAudioStream();
|
||||
stream.CoreStream.StreamType = TSStreamType.DTS_AUDIO;
|
||||
}
|
||||
if (!stream.CoreStream.IsInitialized)
|
||||
{
|
||||
buffer.BeginRead();
|
||||
TSCodecDTS.Scan(stream.CoreStream, buffer, bitrate, ref tag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tag = "HD";
|
||||
int temp1 = buffer.ReadBits(8);
|
||||
int nuSubStreamIndex = buffer.ReadBits(2);
|
||||
int nuExtSSHeaderSize = 0;
|
||||
int nuExtSSFSize = 0;
|
||||
int bBlownUpHeader = buffer.ReadBits(1);
|
||||
if (1 == bBlownUpHeader)
|
||||
{
|
||||
nuExtSSHeaderSize = buffer.ReadBits(12) + 1;
|
||||
nuExtSSFSize = buffer.ReadBits(20) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nuExtSSHeaderSize = buffer.ReadBits(8) + 1;
|
||||
nuExtSSFSize = buffer.ReadBits(16) + 1;
|
||||
}
|
||||
int nuNumAudioPresent = 1;
|
||||
int nuNumAssets = 1;
|
||||
int bStaticFieldsPresent = buffer.ReadBits(1);
|
||||
if (1 == bStaticFieldsPresent)
|
||||
{
|
||||
int nuRefClockCode = buffer.ReadBits(2);
|
||||
int nuExSSFrameDurationCode = buffer.ReadBits(3) + 1;
|
||||
long nuTimeStamp = 0;
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
nuTimeStamp = (buffer.ReadBits(18) << 18) + buffer.ReadBits(18);
|
||||
}
|
||||
nuNumAudioPresent = buffer.ReadBits(3) + 1;
|
||||
nuNumAssets = buffer.ReadBits(3) + 1;
|
||||
int[] nuActiveExSSMask = new int[nuNumAudioPresent];
|
||||
for (int i = 0; i < nuNumAudioPresent; i++)
|
||||
{
|
||||
nuActiveExSSMask[i] = buffer.ReadBits(nuSubStreamIndex + 1); //?
|
||||
}
|
||||
for (int i = 0; i < nuNumAudioPresent; i++)
|
||||
{
|
||||
for (int j = 0; j < nuSubStreamIndex + 1; j++)
|
||||
{
|
||||
if (((j + 1) % 2) == 1)
|
||||
{
|
||||
int mask = buffer.ReadBits(8);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (1 == buffer.ReadBits(1))
|
||||
{
|
||||
int nuMixMetadataAdjLevel = buffer.ReadBits(2);
|
||||
int nuBits4MixOutMask = buffer.ReadBits(2) * 4 + 4;
|
||||
int nuNumMixOutConfigs = buffer.ReadBits(2) + 1;
|
||||
int[] nuMixOutChMask = new int[nuNumMixOutConfigs];
|
||||
for (int i = 0; i < nuNumMixOutConfigs; i++)
|
||||
{
|
||||
nuMixOutChMask[i] = buffer.ReadBits(nuBits4MixOutMask);
|
||||
}
|
||||
}
|
||||
}
|
||||
int[] AssetSizes = new int[nuNumAssets];
|
||||
for (int i = 0; i < nuNumAssets; i++)
|
||||
{
|
||||
if (1 == bBlownUpHeader)
|
||||
{
|
||||
AssetSizes[i] = buffer.ReadBits(20) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetSizes[i] = buffer.ReadBits(16) + 1;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < nuNumAssets; i++)
|
||||
{
|
||||
long bufferPosition = buffer.Position;
|
||||
int nuAssetDescriptorFSIZE = buffer.ReadBits(9) + 1;
|
||||
int DescriptorDataForAssetIndex = buffer.ReadBits(3);
|
||||
if (1 == bStaticFieldsPresent)
|
||||
{
|
||||
int AssetTypeDescrPresent = buffer.ReadBits(1);
|
||||
if (1 == AssetTypeDescrPresent)
|
||||
{
|
||||
int AssetTypeDescriptor = buffer.ReadBits(4);
|
||||
}
|
||||
int LanguageDescrPresent = buffer.ReadBits(1);
|
||||
if (1 == LanguageDescrPresent)
|
||||
{
|
||||
int LanguageDescriptor = buffer.ReadBits(24);
|
||||
}
|
||||
int bInfoTextPresent = buffer.ReadBits(1);
|
||||
if (1 == bInfoTextPresent)
|
||||
{
|
||||
int nuInfoTextByteSize = buffer.ReadBits(10) + 1;
|
||||
int[] InfoText = new int[nuInfoTextByteSize];
|
||||
for (int j = 0; j < nuInfoTextByteSize; j++)
|
||||
{
|
||||
InfoText[j] = buffer.ReadBits(8);
|
||||
}
|
||||
}
|
||||
int nuBitResolution = buffer.ReadBits(5) + 1;
|
||||
int nuMaxSampleRate = buffer.ReadBits(4);
|
||||
int nuTotalNumChs = buffer.ReadBits(8) + 1;
|
||||
int bOne2OneMapChannels2Speakers = buffer.ReadBits(1);
|
||||
int nuSpkrActivityMask = 0;
|
||||
if (1 == bOne2OneMapChannels2Speakers)
|
||||
{
|
||||
int bEmbeddedStereoFlag = 0;
|
||||
if (nuTotalNumChs > 2)
|
||||
{
|
||||
bEmbeddedStereoFlag = buffer.ReadBits(1);
|
||||
}
|
||||
int bEmbeddedSixChFlag = 0;
|
||||
if (nuTotalNumChs > 6)
|
||||
{
|
||||
bEmbeddedSixChFlag = buffer.ReadBits(1);
|
||||
}
|
||||
int bSpkrMaskEnabled = buffer.ReadBits(1);
|
||||
int nuNumBits4SAMask = 0;
|
||||
if (1 == bSpkrMaskEnabled)
|
||||
{
|
||||
nuNumBits4SAMask = buffer.ReadBits(2);
|
||||
nuNumBits4SAMask = nuNumBits4SAMask * 4 + 4;
|
||||
nuSpkrActivityMask = buffer.ReadBits(nuNumBits4SAMask);
|
||||
}
|
||||
// TODO...
|
||||
}
|
||||
stream.SampleRate = SampleRates[nuMaxSampleRate];
|
||||
stream.BitDepth = nuBitResolution;
|
||||
|
||||
stream.LFE = 0;
|
||||
if ((nuSpkrActivityMask & 0x8) == 0x8)
|
||||
{
|
||||
++stream.LFE;
|
||||
}
|
||||
if ((nuSpkrActivityMask & 0x1000) == 0x1000)
|
||||
{
|
||||
++stream.LFE;
|
||||
}
|
||||
stream.ChannelCount = nuTotalNumChs - stream.LFE;
|
||||
}
|
||||
if (nuNumAssets > 1)
|
||||
{
|
||||
// TODO...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
if (stream.CoreStream != null)
|
||||
{
|
||||
var coreStream = (TSAudioStream)stream.CoreStream;
|
||||
if (coreStream.AudioMode == TSAudioMode.Extended &&
|
||||
stream.ChannelCount == 5)
|
||||
{
|
||||
stream.AudioMode = TSAudioMode.Extended;
|
||||
}
|
||||
/*
|
||||
if (coreStream.DialNorm != 0)
|
||||
{
|
||||
stream.DialNorm = coreStream.DialNorm;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (stream.StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
|
||||
{
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
else if (bitrate > 0)
|
||||
{
|
||||
stream.IsVBR = false;
|
||||
stream.BitRate = bitrate;
|
||||
if (stream.CoreStream != null)
|
||||
{
|
||||
stream.BitRate += stream.CoreStream.BitRate;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
stream.IsInitialized = (stream.BitRate > 0 ? true : false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
BDInfo/TSCodecLPCM.cs
Normal file
123
BDInfo/TSCodecLPCM.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecLPCM
|
||||
{
|
||||
public static void Scan(
|
||||
TSAudioStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
if (stream.IsInitialized) return;
|
||||
|
||||
byte[] header = buffer.ReadBytes(4);
|
||||
int flags = (header[2] << 8) + header[3];
|
||||
|
||||
switch ((flags & 0xF000) >> 12)
|
||||
{
|
||||
case 1: // 1/0/0
|
||||
stream.ChannelCount = 1;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 3: // 2/0/0
|
||||
stream.ChannelCount = 2;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 4: // 3/0/0
|
||||
stream.ChannelCount = 3;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 5: // 2/1/0
|
||||
stream.ChannelCount = 3;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 6: // 3/1/0
|
||||
stream.ChannelCount = 4;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 7: // 2/2/0
|
||||
stream.ChannelCount = 4;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 8: // 3/2/0
|
||||
stream.ChannelCount = 5;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 9: // 3/2/1
|
||||
stream.ChannelCount = 5;
|
||||
stream.LFE = 1;
|
||||
break;
|
||||
case 10: // 3/4/0
|
||||
stream.ChannelCount = 7;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
case 11: // 3/4/1
|
||||
stream.ChannelCount = 7;
|
||||
stream.LFE = 1;
|
||||
break;
|
||||
default:
|
||||
stream.ChannelCount = 0;
|
||||
stream.LFE = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch ((flags & 0xC0) >> 6)
|
||||
{
|
||||
case 1:
|
||||
stream.BitDepth = 16;
|
||||
break;
|
||||
case 2:
|
||||
stream.BitDepth = 20;
|
||||
break;
|
||||
case 3:
|
||||
stream.BitDepth = 24;
|
||||
break;
|
||||
default:
|
||||
stream.BitDepth = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
switch ((flags & 0xF00) >> 8)
|
||||
{
|
||||
case 1:
|
||||
stream.SampleRate = 48000;
|
||||
break;
|
||||
case 4:
|
||||
stream.SampleRate = 96000;
|
||||
break;
|
||||
case 5:
|
||||
stream.SampleRate = 192000;
|
||||
break;
|
||||
default:
|
||||
stream.SampleRate = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
stream.BitRate = (uint)
|
||||
(stream.SampleRate * stream.BitDepth *
|
||||
(stream.ChannelCount + stream.LFE));
|
||||
|
||||
stream.IsVBR = false;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
208
BDInfo/TSCodecMPEG2.cs
Normal file
208
BDInfo/TSCodecMPEG2.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
#undef DEBUG
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecMPEG2
|
||||
{
|
||||
public static void Scan(
|
||||
TSVideoStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
int parse = 0;
|
||||
int pictureParse = 0;
|
||||
int sequenceHeaderParse = 0;
|
||||
int extensionParse = 0;
|
||||
int sequenceExtensionParse = 0;
|
||||
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
parse = (parse << 8) + buffer.ReadByte();
|
||||
|
||||
if (parse == 0x00000100)
|
||||
{
|
||||
pictureParse = 2;
|
||||
}
|
||||
else if (parse == 0x000001B3)
|
||||
{
|
||||
sequenceHeaderParse = 7;
|
||||
}
|
||||
else if (sequenceHeaderParse > 0)
|
||||
{
|
||||
--sequenceHeaderParse;
|
||||
switch (sequenceHeaderParse)
|
||||
{
|
||||
#if DEBUG
|
||||
case 6:
|
||||
break;
|
||||
|
||||
case 5:
|
||||
break;
|
||||
|
||||
case 4:
|
||||
stream.Width =
|
||||
(int)((parse & 0xFFF000) >> 12);
|
||||
stream.Height =
|
||||
(int)(parse & 0xFFF);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
stream.AspectRatio =
|
||||
(TSAspectRatio)((parse & 0xF0) >> 4);
|
||||
|
||||
switch ((parse & 0xF0) >> 4)
|
||||
{
|
||||
case 0: // Forbidden
|
||||
break;
|
||||
case 1: // Square
|
||||
break;
|
||||
case 2: // 4:3
|
||||
break;
|
||||
case 3: // 16:9
|
||||
break;
|
||||
case 4: // 2.21:1
|
||||
break;
|
||||
default: // Reserved
|
||||
break;
|
||||
}
|
||||
|
||||
switch (parse & 0xF)
|
||||
{
|
||||
case 0: // Forbidden
|
||||
break;
|
||||
case 1: // 23.976
|
||||
stream.FrameRateEnumerator = 24000;
|
||||
stream.FrameRateDenominator = 1001;
|
||||
break;
|
||||
case 2: // 24
|
||||
stream.FrameRateEnumerator = 24000;
|
||||
stream.FrameRateDenominator = 1000;
|
||||
break;
|
||||
case 3: // 25
|
||||
stream.FrameRateEnumerator = 25000;
|
||||
stream.FrameRateDenominator = 1000;
|
||||
break;
|
||||
case 4: // 29.97
|
||||
stream.FrameRateEnumerator = 30000;
|
||||
stream.FrameRateDenominator = 1001;
|
||||
break;
|
||||
case 5: // 30
|
||||
stream.FrameRateEnumerator = 30000;
|
||||
stream.FrameRateDenominator = 1000;
|
||||
break;
|
||||
case 6: // 50
|
||||
stream.FrameRateEnumerator = 50000;
|
||||
stream.FrameRateDenominator = 1000;
|
||||
break;
|
||||
case 7: // 59.94
|
||||
stream.FrameRateEnumerator = 60000;
|
||||
stream.FrameRateDenominator = 1001;
|
||||
break;
|
||||
case 8: // 60
|
||||
stream.FrameRateEnumerator = 60000;
|
||||
stream.FrameRateDenominator = 1000;
|
||||
break;
|
||||
default: // Reserved
|
||||
stream.FrameRateEnumerator = 0;
|
||||
stream.FrameRateDenominator = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
break;
|
||||
|
||||
case 1:
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 0:
|
||||
#if DEBUG
|
||||
stream.BitRate =
|
||||
(((parse & 0xFFFFC0) >> 6) * 200);
|
||||
#endif
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (pictureParse > 0)
|
||||
{
|
||||
--pictureParse;
|
||||
if (pictureParse == 0)
|
||||
{
|
||||
switch ((parse & 0x38) >> 3)
|
||||
{
|
||||
case 1:
|
||||
tag = "I";
|
||||
break;
|
||||
case 2:
|
||||
tag = "P";
|
||||
break;
|
||||
case 3:
|
||||
tag = "B";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (stream.IsInitialized) return;
|
||||
}
|
||||
}
|
||||
else if (parse == 0x000001B5)
|
||||
{
|
||||
extensionParse = 1;
|
||||
}
|
||||
else if (extensionParse > 0)
|
||||
{
|
||||
--extensionParse;
|
||||
if (extensionParse == 0)
|
||||
{
|
||||
if ((parse & 0xF0) == 0x10)
|
||||
{
|
||||
sequenceExtensionParse = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sequenceExtensionParse > 0)
|
||||
{
|
||||
--sequenceExtensionParse;
|
||||
#if DEBUG
|
||||
if (sequenceExtensionParse == 0)
|
||||
{
|
||||
uint sequenceExtension =
|
||||
((parse & 0x8) >> 3);
|
||||
if (sequenceExtension == 0)
|
||||
{
|
||||
stream.IsInterlaced = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.IsInterlaced = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
BDInfo/TSCodecMVC.cs
Normal file
36
BDInfo/TSCodecMVC.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
// TODO: Do something more interesting here...
|
||||
|
||||
public abstract class TSCodecMVC
|
||||
{
|
||||
public static void Scan(
|
||||
TSVideoStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
BDInfo/TSCodecTrueHD.cs
Normal file
186
BDInfo/TSCodecTrueHD.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecTrueHD
|
||||
{
|
||||
public static void Scan(
|
||||
TSAudioStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
if (stream.IsInitialized &&
|
||||
stream.CoreStream != null &&
|
||||
stream.CoreStream.IsInitialized) return;
|
||||
|
||||
bool syncFound = false;
|
||||
uint sync = 0;
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
sync = (sync << 8) + buffer.ReadByte();
|
||||
if (sync == 0xF8726FBA)
|
||||
{
|
||||
syncFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!syncFound)
|
||||
{
|
||||
tag = "CORE";
|
||||
if (stream.CoreStream == null)
|
||||
{
|
||||
stream.CoreStream = new TSAudioStream();
|
||||
stream.CoreStream.StreamType = TSStreamType.AC3_AUDIO;
|
||||
}
|
||||
if (!stream.CoreStream.IsInitialized)
|
||||
{
|
||||
buffer.BeginRead();
|
||||
TSCodecAC3.Scan(stream.CoreStream, buffer, ref tag);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tag = "HD";
|
||||
int ratebits = buffer.ReadBits(4);
|
||||
if (ratebits != 0xF)
|
||||
{
|
||||
stream.SampleRate =
|
||||
(((ratebits & 8) > 0 ? 44100 : 48000) << (ratebits & 7));
|
||||
}
|
||||
int temp1 = buffer.ReadBits(8);
|
||||
int channels_thd_stream1 = buffer.ReadBits(5);
|
||||
int temp2 = buffer.ReadBits(2);
|
||||
|
||||
stream.ChannelCount = 0;
|
||||
stream.LFE = 0;
|
||||
int c_LFE2 = buffer.ReadBits(1);
|
||||
if (c_LFE2 == 1)
|
||||
{
|
||||
stream.LFE += 1;
|
||||
}
|
||||
int c_Cvh = buffer.ReadBits(1);
|
||||
if (c_Cvh == 1)
|
||||
{
|
||||
stream.ChannelCount += 1;
|
||||
}
|
||||
int c_LRw = buffer.ReadBits(1);
|
||||
if (c_LRw == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_LRsd = buffer.ReadBits(1);
|
||||
if (c_LRsd == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_Ts = buffer.ReadBits(1);
|
||||
if (c_Ts == 1)
|
||||
{
|
||||
stream.ChannelCount += 1;
|
||||
}
|
||||
int c_Cs = buffer.ReadBits(1);
|
||||
if (c_Cs == 1)
|
||||
{
|
||||
stream.ChannelCount += 1;
|
||||
}
|
||||
int c_LRrs = buffer.ReadBits(1);
|
||||
if (c_LRrs == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_LRc = buffer.ReadBits(1);
|
||||
if (c_LRc == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_LRvh = buffer.ReadBits(1);
|
||||
if (c_LRvh == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_LRs = buffer.ReadBits(1);
|
||||
if (c_LRs == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
int c_LFE = buffer.ReadBits(1);
|
||||
if (c_LFE == 1)
|
||||
{
|
||||
stream.LFE += 1;
|
||||
}
|
||||
int c_C = buffer.ReadBits(1);
|
||||
if (c_C == 1)
|
||||
{
|
||||
stream.ChannelCount += 1;
|
||||
}
|
||||
int c_LR = buffer.ReadBits(1);
|
||||
if (c_LR == 1)
|
||||
{
|
||||
stream.ChannelCount += 2;
|
||||
}
|
||||
|
||||
int access_unit_size = 40 << (ratebits & 7);
|
||||
int access_unit_size_pow2 = 64 << (ratebits & 7);
|
||||
|
||||
int a1 = buffer.ReadBits(16);
|
||||
int a2 = buffer.ReadBits(16);
|
||||
int a3 = buffer.ReadBits(16);
|
||||
|
||||
int is_vbr = buffer.ReadBits(1);
|
||||
int peak_bitrate = buffer.ReadBits(15);
|
||||
peak_bitrate = (peak_bitrate * stream.SampleRate) >> 4;
|
||||
|
||||
double peak_bitdepth =
|
||||
(double)peak_bitrate /
|
||||
(stream.ChannelCount + stream.LFE) /
|
||||
stream.SampleRate;
|
||||
if (peak_bitdepth > 14)
|
||||
{
|
||||
stream.BitDepth = 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.BitDepth = 16;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.WriteLine(string.Format(
|
||||
"{0}\t{1}\t{2:F2}",
|
||||
stream.PID, peak_bitrate, peak_bitdepth));
|
||||
#endif
|
||||
/*
|
||||
// TODO: Get THD dialnorm from metadata
|
||||
if (stream.CoreStream != null)
|
||||
{
|
||||
TSAudioStream coreStream = (TSAudioStream)stream.CoreStream;
|
||||
if (coreStream.DialNorm != 0)
|
||||
{
|
||||
stream.DialNorm = coreStream.DialNorm;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
131
BDInfo/TSCodecVC1.cs
Normal file
131
BDInfo/TSCodecVC1.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public abstract class TSCodecVC1
|
||||
{
|
||||
public static void Scan(
|
||||
TSVideoStream stream,
|
||||
TSStreamBuffer buffer,
|
||||
ref string tag)
|
||||
{
|
||||
int parse = 0;
|
||||
byte frameHeaderParse = 0;
|
||||
byte sequenceHeaderParse = 0;
|
||||
bool isInterlaced = false;
|
||||
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
parse = (parse << 8) + buffer.ReadByte();
|
||||
|
||||
if (parse == 0x0000010D)
|
||||
{
|
||||
frameHeaderParse = 4;
|
||||
}
|
||||
else if (frameHeaderParse > 0)
|
||||
{
|
||||
--frameHeaderParse;
|
||||
if (frameHeaderParse == 0)
|
||||
{
|
||||
uint pictureType = 0;
|
||||
if (isInterlaced)
|
||||
{
|
||||
if ((parse & 0x80000000) == 0)
|
||||
{
|
||||
pictureType =
|
||||
(uint)((parse & 0x78000000) >> 13);
|
||||
}
|
||||
else
|
||||
{
|
||||
pictureType =
|
||||
(uint)((parse & 0x3c000000) >> 12);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pictureType =
|
||||
(uint)((parse & 0xf0000000) >> 14);
|
||||
}
|
||||
|
||||
if ((pictureType & 0x20000) == 0)
|
||||
{
|
||||
tag = "P";
|
||||
}
|
||||
else if ((pictureType & 0x10000) == 0)
|
||||
{
|
||||
tag = "B";
|
||||
}
|
||||
else if ((pictureType & 0x8000) == 0)
|
||||
{
|
||||
tag = "I";
|
||||
}
|
||||
else if ((pictureType & 0x4000) == 0)
|
||||
{
|
||||
tag = "BI";
|
||||
}
|
||||
else
|
||||
{
|
||||
tag = null;
|
||||
}
|
||||
if (stream.IsInitialized) return;
|
||||
}
|
||||
}
|
||||
else if (parse == 0x0000010F)
|
||||
{
|
||||
sequenceHeaderParse = 6;
|
||||
}
|
||||
else if (sequenceHeaderParse > 0)
|
||||
{
|
||||
--sequenceHeaderParse;
|
||||
switch (sequenceHeaderParse)
|
||||
{
|
||||
case 5:
|
||||
int profileLevel = ((parse & 0x38) >> 3);
|
||||
if (((parse & 0xC0) >> 6) == 3)
|
||||
{
|
||||
stream.EncodingProfile = string.Format(
|
||||
"Advanced Profile {0}", profileLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.EncodingProfile = string.Format(
|
||||
"Main Profile {0}", profileLevel);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0:
|
||||
if (((parse & 0x40) >> 6) > 0)
|
||||
{
|
||||
isInterlaced = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isInterlaced = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
stream.IsVBR = true;
|
||||
stream.IsInitialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
BDInfo/TSInterleavedFile.cs
Normal file
37
BDInfo/TSInterleavedFile.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
// TODO: Do more interesting things here...
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public class TSInterleavedFile
|
||||
{
|
||||
public FileSystemMetadata FileInfo = null;
|
||||
public string Name = null;
|
||||
|
||||
public TSInterleavedFile(FileSystemMetadata fileInfo)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
}
|
||||
}
|
||||
1282
BDInfo/TSPlaylistFile.cs
Normal file
1282
BDInfo/TSPlaylistFile.cs
Normal file
File diff suppressed because it is too large
Load Diff
780
BDInfo/TSStream.cs
Normal file
780
BDInfo/TSStream.cs
Normal file
@@ -0,0 +1,780 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public enum TSStreamType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
MPEG1_VIDEO = 0x01,
|
||||
MPEG2_VIDEO = 0x02,
|
||||
AVC_VIDEO = 0x1b,
|
||||
MVC_VIDEO = 0x20,
|
||||
VC1_VIDEO = 0xea,
|
||||
MPEG1_AUDIO = 0x03,
|
||||
MPEG2_AUDIO = 0x04,
|
||||
LPCM_AUDIO = 0x80,
|
||||
AC3_AUDIO = 0x81,
|
||||
AC3_PLUS_AUDIO = 0x84,
|
||||
AC3_PLUS_SECONDARY_AUDIO = 0xA1,
|
||||
AC3_TRUE_HD_AUDIO = 0x83,
|
||||
DTS_AUDIO = 0x82,
|
||||
DTS_HD_AUDIO = 0x85,
|
||||
DTS_HD_SECONDARY_AUDIO = 0xA2,
|
||||
DTS_HD_MASTER_AUDIO = 0x86,
|
||||
PRESENTATION_GRAPHICS = 0x90,
|
||||
INTERACTIVE_GRAPHICS = 0x91,
|
||||
SUBTITLE = 0x92
|
||||
}
|
||||
|
||||
public enum TSVideoFormat : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
VIDEOFORMAT_480i = 1,
|
||||
VIDEOFORMAT_576i = 2,
|
||||
VIDEOFORMAT_480p = 3,
|
||||
VIDEOFORMAT_1080i = 4,
|
||||
VIDEOFORMAT_720p = 5,
|
||||
VIDEOFORMAT_1080p = 6,
|
||||
VIDEOFORMAT_576p = 7,
|
||||
}
|
||||
|
||||
public enum TSFrameRate : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
FRAMERATE_23_976 = 1,
|
||||
FRAMERATE_24 = 2,
|
||||
FRAMERATE_25 = 3,
|
||||
FRAMERATE_29_97 = 4,
|
||||
FRAMERATE_50 = 6,
|
||||
FRAMERATE_59_94 = 7
|
||||
}
|
||||
|
||||
public enum TSChannelLayout : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
CHANNELLAYOUT_MONO = 1,
|
||||
CHANNELLAYOUT_STEREO = 3,
|
||||
CHANNELLAYOUT_MULTI = 6,
|
||||
CHANNELLAYOUT_COMBO = 12
|
||||
}
|
||||
|
||||
public enum TSSampleRate : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
SAMPLERATE_48 = 1,
|
||||
SAMPLERATE_96 = 4,
|
||||
SAMPLERATE_192 = 5,
|
||||
SAMPLERATE_48_192 = 12,
|
||||
SAMPLERATE_48_96 = 14
|
||||
}
|
||||
|
||||
public enum TSAspectRatio
|
||||
{
|
||||
Unknown = 0,
|
||||
ASPECT_4_3 = 2,
|
||||
ASPECT_16_9 = 3,
|
||||
ASPECT_2_21 = 4
|
||||
}
|
||||
|
||||
public class TSDescriptor
|
||||
{
|
||||
public byte Name;
|
||||
public byte[] Value;
|
||||
|
||||
public TSDescriptor(byte name, byte length)
|
||||
{
|
||||
Name = name;
|
||||
Value = new byte[length];
|
||||
}
|
||||
|
||||
public TSDescriptor Clone()
|
||||
{
|
||||
var descriptor =
|
||||
new TSDescriptor(Name, (byte)Value.Length);
|
||||
Value.CopyTo(descriptor.Value, 0);
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TSStream
|
||||
{
|
||||
public TSStream()
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1})", CodecShortName, PID);
|
||||
}
|
||||
|
||||
public ushort PID;
|
||||
public TSStreamType StreamType;
|
||||
public List<TSDescriptor> Descriptors = null;
|
||||
public long BitRate = 0;
|
||||
public long ActiveBitRate = 0;
|
||||
public bool IsVBR = false;
|
||||
public bool IsInitialized = false;
|
||||
public string LanguageName;
|
||||
public bool IsHidden = false;
|
||||
|
||||
public ulong PayloadBytes = 0;
|
||||
public ulong PacketCount = 0;
|
||||
public double PacketSeconds = 0;
|
||||
public int AngleIndex = 0;
|
||||
|
||||
public ulong PacketSize => PacketCount * 192;
|
||||
|
||||
private string _LanguageCode;
|
||||
public string LanguageCode
|
||||
{
|
||||
get => _LanguageCode;
|
||||
set
|
||||
{
|
||||
_LanguageCode = value;
|
||||
LanguageName = LanguageCodes.GetName(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsVideoStream
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.MPEG1_VIDEO:
|
||||
case TSStreamType.MPEG2_VIDEO:
|
||||
case TSStreamType.AVC_VIDEO:
|
||||
case TSStreamType.MVC_VIDEO:
|
||||
case TSStreamType.VC1_VIDEO:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsAudioStream
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.MPEG1_AUDIO:
|
||||
case TSStreamType.MPEG2_AUDIO:
|
||||
case TSStreamType.LPCM_AUDIO:
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
case TSStreamType.DTS_HD_AUDIO:
|
||||
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsGraphicsStream
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTextStream
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.SUBTITLE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CodecName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.MPEG1_VIDEO:
|
||||
return "MPEG-1 Video";
|
||||
case TSStreamType.MPEG2_VIDEO:
|
||||
return "MPEG-2 Video";
|
||||
case TSStreamType.AVC_VIDEO:
|
||||
return "MPEG-4 AVC Video";
|
||||
case TSStreamType.MVC_VIDEO:
|
||||
return "MPEG-4 MVC Video";
|
||||
case TSStreamType.VC1_VIDEO:
|
||||
return "VC-1 Video";
|
||||
case TSStreamType.MPEG1_AUDIO:
|
||||
return "MP1 Audio";
|
||||
case TSStreamType.MPEG2_AUDIO:
|
||||
return "MP2 Audio";
|
||||
case TSStreamType.LPCM_AUDIO:
|
||||
return "LPCM Audio";
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
|
||||
return "Dolby Digital EX Audio";
|
||||
else
|
||||
return "Dolby Digital Audio";
|
||||
case TSStreamType.AC3_PLUS_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||
return "Dolby Digital Plus Audio";
|
||||
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||
return "Dolby TrueHD Audio";
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
|
||||
return "DTS-ES Audio";
|
||||
else
|
||||
return "DTS Audio";
|
||||
case TSStreamType.DTS_HD_AUDIO:
|
||||
return "DTS-HD High-Res Audio";
|
||||
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||
return "DTS Express";
|
||||
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||
return "DTS-HD Master Audio";
|
||||
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||
return "Presentation Graphics";
|
||||
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||
return "Interactive Graphics";
|
||||
case TSStreamType.SUBTITLE:
|
||||
return "Subtitle";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CodecAltName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.MPEG1_VIDEO:
|
||||
return "MPEG-1";
|
||||
case TSStreamType.MPEG2_VIDEO:
|
||||
return "MPEG-2";
|
||||
case TSStreamType.AVC_VIDEO:
|
||||
return "AVC";
|
||||
case TSStreamType.MVC_VIDEO:
|
||||
return "MVC";
|
||||
case TSStreamType.VC1_VIDEO:
|
||||
return "VC-1";
|
||||
case TSStreamType.MPEG1_AUDIO:
|
||||
return "MP1";
|
||||
case TSStreamType.MPEG2_AUDIO:
|
||||
return "MP2";
|
||||
case TSStreamType.LPCM_AUDIO:
|
||||
return "LPCM";
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
return "DD AC3";
|
||||
case TSStreamType.AC3_PLUS_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||
return "DD AC3+";
|
||||
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||
return "Dolby TrueHD";
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
return "DTS";
|
||||
case TSStreamType.DTS_HD_AUDIO:
|
||||
return "DTS-HD Hi-Res";
|
||||
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||
return "DTS Express";
|
||||
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||
return "DTS-HD Master";
|
||||
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||
return "PGS";
|
||||
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||
return "IGS";
|
||||
case TSStreamType.SUBTITLE:
|
||||
return "SUB";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string CodecShortName
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (StreamType)
|
||||
{
|
||||
case TSStreamType.MPEG1_VIDEO:
|
||||
return "MPEG-1";
|
||||
case TSStreamType.MPEG2_VIDEO:
|
||||
return "MPEG-2";
|
||||
case TSStreamType.AVC_VIDEO:
|
||||
return "AVC";
|
||||
case TSStreamType.MVC_VIDEO:
|
||||
return "MVC";
|
||||
case TSStreamType.VC1_VIDEO:
|
||||
return "VC-1";
|
||||
case TSStreamType.MPEG1_AUDIO:
|
||||
return "MP1";
|
||||
case TSStreamType.MPEG2_AUDIO:
|
||||
return "MP2";
|
||||
case TSStreamType.LPCM_AUDIO:
|
||||
return "LPCM";
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
|
||||
return "AC3-EX";
|
||||
else
|
||||
return "AC3";
|
||||
case TSStreamType.AC3_PLUS_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||
return "AC3+";
|
||||
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||
return "TrueHD";
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
if (((TSAudioStream)this).AudioMode == TSAudioMode.Extended)
|
||||
return "DTS-ES";
|
||||
else
|
||||
return "DTS";
|
||||
case TSStreamType.DTS_HD_AUDIO:
|
||||
return "DTS-HD HR";
|
||||
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||
return "DTS Express";
|
||||
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||
return "DTS-HD MA";
|
||||
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||
return "PGS";
|
||||
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||
return "IGS";
|
||||
case TSStreamType.SUBTITLE:
|
||||
return "SUB";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string Description => "";
|
||||
|
||||
public abstract TSStream Clone();
|
||||
|
||||
protected void CopyTo(TSStream stream)
|
||||
{
|
||||
stream.PID = PID;
|
||||
stream.StreamType = StreamType;
|
||||
stream.IsVBR = IsVBR;
|
||||
stream.BitRate = BitRate;
|
||||
stream.IsInitialized = IsInitialized;
|
||||
stream.LanguageCode = _LanguageCode;
|
||||
if (Descriptors != null)
|
||||
{
|
||||
stream.Descriptors = new List<TSDescriptor>();
|
||||
foreach (var descriptor in Descriptors)
|
||||
{
|
||||
stream.Descriptors.Add(descriptor.Clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class TSVideoStream : TSStream
|
||||
{
|
||||
public TSVideoStream()
|
||||
{
|
||||
}
|
||||
|
||||
public int Width;
|
||||
public int Height;
|
||||
public bool IsInterlaced;
|
||||
public int FrameRateEnumerator;
|
||||
public int FrameRateDenominator;
|
||||
public TSAspectRatio AspectRatio;
|
||||
public string EncodingProfile;
|
||||
|
||||
private TSVideoFormat _VideoFormat;
|
||||
public TSVideoFormat VideoFormat
|
||||
{
|
||||
get => _VideoFormat;
|
||||
set
|
||||
{
|
||||
_VideoFormat = value;
|
||||
switch (value)
|
||||
{
|
||||
case TSVideoFormat.VIDEOFORMAT_480i:
|
||||
Height = 480;
|
||||
IsInterlaced = true;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_480p:
|
||||
Height = 480;
|
||||
IsInterlaced = false;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_576i:
|
||||
Height = 576;
|
||||
IsInterlaced = true;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_576p:
|
||||
Height = 576;
|
||||
IsInterlaced = false;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_720p:
|
||||
Height = 720;
|
||||
IsInterlaced = false;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_1080i:
|
||||
Height = 1080;
|
||||
IsInterlaced = true;
|
||||
break;
|
||||
case TSVideoFormat.VIDEOFORMAT_1080p:
|
||||
Height = 1080;
|
||||
IsInterlaced = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TSFrameRate _FrameRate;
|
||||
public TSFrameRate FrameRate
|
||||
{
|
||||
get => _FrameRate;
|
||||
set
|
||||
{
|
||||
_FrameRate = value;
|
||||
switch (value)
|
||||
{
|
||||
case TSFrameRate.FRAMERATE_23_976:
|
||||
FrameRateEnumerator = 24000;
|
||||
FrameRateDenominator = 1001;
|
||||
break;
|
||||
case TSFrameRate.FRAMERATE_24:
|
||||
FrameRateEnumerator = 24000;
|
||||
FrameRateDenominator = 1000;
|
||||
break;
|
||||
case TSFrameRate.FRAMERATE_25:
|
||||
FrameRateEnumerator = 25000;
|
||||
FrameRateDenominator = 1000;
|
||||
break;
|
||||
case TSFrameRate.FRAMERATE_29_97:
|
||||
FrameRateEnumerator = 30000;
|
||||
FrameRateDenominator = 1001;
|
||||
break;
|
||||
case TSFrameRate.FRAMERATE_50:
|
||||
FrameRateEnumerator = 50000;
|
||||
FrameRateDenominator = 1000;
|
||||
break;
|
||||
case TSFrameRate.FRAMERATE_59_94:
|
||||
FrameRateEnumerator = 60000;
|
||||
FrameRateDenominator = 1001;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
string description = "";
|
||||
|
||||
if (Height > 0)
|
||||
{
|
||||
description += string.Format("{0:D}{1} / ",
|
||||
Height,
|
||||
IsInterlaced ? "i" : "p");
|
||||
}
|
||||
if (FrameRateEnumerator > 0 &&
|
||||
FrameRateDenominator > 0)
|
||||
{
|
||||
if (FrameRateEnumerator % FrameRateDenominator == 0)
|
||||
{
|
||||
description += string.Format("{0:D} fps / ",
|
||||
FrameRateEnumerator / FrameRateDenominator);
|
||||
}
|
||||
else
|
||||
{
|
||||
description += string.Format("{0:F3} fps / ",
|
||||
(double)FrameRateEnumerator / FrameRateDenominator);
|
||||
}
|
||||
|
||||
}
|
||||
if (AspectRatio == TSAspectRatio.ASPECT_4_3)
|
||||
{
|
||||
description += "4:3 / ";
|
||||
}
|
||||
else if (AspectRatio == TSAspectRatio.ASPECT_16_9)
|
||||
{
|
||||
description += "16:9 / ";
|
||||
}
|
||||
if (EncodingProfile != null)
|
||||
{
|
||||
description += EncodingProfile + " / ";
|
||||
}
|
||||
if (description.EndsWith(" / "))
|
||||
{
|
||||
description = description.Substring(0, description.Length - 3);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public override TSStream Clone()
|
||||
{
|
||||
var stream = new TSVideoStream();
|
||||
CopyTo(stream);
|
||||
|
||||
stream.VideoFormat = _VideoFormat;
|
||||
stream.FrameRate = _FrameRate;
|
||||
stream.Width = Width;
|
||||
stream.Height = Height;
|
||||
stream.IsInterlaced = IsInterlaced;
|
||||
stream.FrameRateEnumerator = FrameRateEnumerator;
|
||||
stream.FrameRateDenominator = FrameRateDenominator;
|
||||
stream.AspectRatio = AspectRatio;
|
||||
stream.EncodingProfile = EncodingProfile;
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TSAudioMode
|
||||
{
|
||||
Unknown,
|
||||
DualMono,
|
||||
Stereo,
|
||||
Surround,
|
||||
Extended
|
||||
}
|
||||
|
||||
public class TSAudioStream : TSStream
|
||||
{
|
||||
public TSAudioStream()
|
||||
{
|
||||
}
|
||||
|
||||
public int SampleRate;
|
||||
public int ChannelCount;
|
||||
public int BitDepth;
|
||||
public int LFE;
|
||||
public int DialNorm;
|
||||
public TSAudioMode AudioMode;
|
||||
public TSAudioStream CoreStream;
|
||||
public TSChannelLayout ChannelLayout;
|
||||
|
||||
public static int ConvertSampleRate(
|
||||
TSSampleRate sampleRate)
|
||||
{
|
||||
switch (sampleRate)
|
||||
{
|
||||
case TSSampleRate.SAMPLERATE_48:
|
||||
return 48000;
|
||||
|
||||
case TSSampleRate.SAMPLERATE_96:
|
||||
case TSSampleRate.SAMPLERATE_48_96:
|
||||
return 96000;
|
||||
|
||||
case TSSampleRate.SAMPLERATE_192:
|
||||
case TSSampleRate.SAMPLERATE_48_192:
|
||||
return 192000;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public string ChannelDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ChannelLayout == TSChannelLayout.CHANNELLAYOUT_MONO &&
|
||||
ChannelCount == 2)
|
||||
{
|
||||
}
|
||||
|
||||
string description = "";
|
||||
if (ChannelCount > 0)
|
||||
{
|
||||
description += string.Format(
|
||||
"{0:D}.{1:D}",
|
||||
ChannelCount, LFE);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (ChannelLayout)
|
||||
{
|
||||
case TSChannelLayout.CHANNELLAYOUT_MONO:
|
||||
description += "1.0";
|
||||
break;
|
||||
case TSChannelLayout.CHANNELLAYOUT_STEREO:
|
||||
description += "2.0";
|
||||
break;
|
||||
case TSChannelLayout.CHANNELLAYOUT_MULTI:
|
||||
description += "5.1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (AudioMode == TSAudioMode.Extended)
|
||||
{
|
||||
if (StreamType == TSStreamType.AC3_AUDIO)
|
||||
{
|
||||
description += "-EX";
|
||||
}
|
||||
if (StreamType == TSStreamType.DTS_AUDIO ||
|
||||
StreamType == TSStreamType.DTS_HD_AUDIO ||
|
||||
StreamType == TSStreamType.DTS_HD_MASTER_AUDIO)
|
||||
{
|
||||
description += "-ES";
|
||||
}
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
string description = ChannelDescription;
|
||||
|
||||
if (SampleRate > 0)
|
||||
{
|
||||
description += string.Format(
|
||||
" / {0:D} kHz", SampleRate / 1000);
|
||||
}
|
||||
if (BitRate > 0)
|
||||
{
|
||||
description += string.Format(
|
||||
" / {0:D} kbps", (uint)Math.Round((double)BitRate / 1000));
|
||||
}
|
||||
if (BitDepth > 0)
|
||||
{
|
||||
description += string.Format(
|
||||
" / {0:D}-bit", BitDepth);
|
||||
}
|
||||
if (DialNorm != 0)
|
||||
{
|
||||
description += string.Format(
|
||||
" / DN {0}dB", DialNorm);
|
||||
}
|
||||
if (ChannelCount == 2)
|
||||
{
|
||||
switch (AudioMode)
|
||||
{
|
||||
case TSAudioMode.DualMono:
|
||||
description += " / Dual Mono";
|
||||
break;
|
||||
|
||||
case TSAudioMode.Surround:
|
||||
description += " / Dolby Surround";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (description.EndsWith(" / "))
|
||||
{
|
||||
description = description.Substring(0, description.Length - 3);
|
||||
}
|
||||
if (CoreStream != null)
|
||||
{
|
||||
string codec = "";
|
||||
switch (CoreStream.StreamType)
|
||||
{
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
codec = "AC3 Embedded";
|
||||
break;
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
codec = "DTS Core";
|
||||
break;
|
||||
}
|
||||
description += string.Format(
|
||||
" ({0}: {1})",
|
||||
codec,
|
||||
CoreStream.Description);
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
public override TSStream Clone()
|
||||
{
|
||||
var stream = new TSAudioStream();
|
||||
CopyTo(stream);
|
||||
|
||||
stream.SampleRate = SampleRate;
|
||||
stream.ChannelLayout = ChannelLayout;
|
||||
stream.ChannelCount = ChannelCount;
|
||||
stream.BitDepth = BitDepth;
|
||||
stream.LFE = LFE;
|
||||
stream.DialNorm = DialNorm;
|
||||
stream.AudioMode = AudioMode;
|
||||
if (CoreStream != null)
|
||||
{
|
||||
stream.CoreStream = (TSAudioStream)CoreStream.Clone();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public class TSGraphicsStream : TSStream
|
||||
{
|
||||
public TSGraphicsStream()
|
||||
{
|
||||
IsVBR = true;
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
public override TSStream Clone()
|
||||
{
|
||||
var stream = new TSGraphicsStream();
|
||||
CopyTo(stream);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public class TSTextStream : TSStream
|
||||
{
|
||||
public TSTextStream()
|
||||
{
|
||||
IsVBR = true;
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
public override TSStream Clone()
|
||||
{
|
||||
var stream = new TSTextStream();
|
||||
CopyTo(stream);
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
130
BDInfo/TSStreamBuffer.cs
Normal file
130
BDInfo/TSStreamBuffer.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.IO;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public class TSStreamBuffer
|
||||
{
|
||||
private MemoryStream Stream = new MemoryStream();
|
||||
private int SkipBits = 0;
|
||||
private byte[] Buffer;
|
||||
private int BufferLength = 0;
|
||||
public int TransferLength = 0;
|
||||
|
||||
public TSStreamBuffer()
|
||||
{
|
||||
Buffer = new byte[4096];
|
||||
Stream = new MemoryStream(Buffer);
|
||||
}
|
||||
|
||||
public long Length => (long)BufferLength;
|
||||
|
||||
public long Position => Stream.Position;
|
||||
|
||||
public void Add(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int length)
|
||||
{
|
||||
TransferLength += length;
|
||||
|
||||
if (BufferLength + length >= Buffer.Length)
|
||||
{
|
||||
length = Buffer.Length - BufferLength;
|
||||
}
|
||||
if (length > 0)
|
||||
{
|
||||
Array.Copy(buffer, offset, Buffer, BufferLength, length);
|
||||
BufferLength += length;
|
||||
}
|
||||
}
|
||||
|
||||
public void Seek(
|
||||
long offset,
|
||||
SeekOrigin loc)
|
||||
{
|
||||
Stream.Seek(offset, loc);
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
BufferLength = 0;
|
||||
TransferLength = 0;
|
||||
}
|
||||
|
||||
public void BeginRead()
|
||||
{
|
||||
SkipBits = 0;
|
||||
Stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public void EndRead()
|
||||
{
|
||||
}
|
||||
|
||||
public byte[] ReadBytes(int bytes)
|
||||
{
|
||||
if (Stream.Position + bytes >= BufferLength)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] value = new byte[bytes];
|
||||
Stream.Read(value, 0, bytes);
|
||||
return value;
|
||||
}
|
||||
|
||||
public byte ReadByte()
|
||||
{
|
||||
return (byte)Stream.ReadByte();
|
||||
}
|
||||
|
||||
public int ReadBits(int bits)
|
||||
{
|
||||
long pos = Stream.Position;
|
||||
|
||||
int shift = 24;
|
||||
int data = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
if (pos + i >= BufferLength) break;
|
||||
data += (Stream.ReadByte() << shift);
|
||||
shift -= 8;
|
||||
}
|
||||
var vector = new BitVector32(data);
|
||||
|
||||
int value = 0;
|
||||
for (int i = SkipBits; i < SkipBits + bits; i++)
|
||||
{
|
||||
value <<= 1;
|
||||
value += (vector[1 << (32 - i - 1)] ? 1 : 0);
|
||||
}
|
||||
|
||||
SkipBits += bits;
|
||||
Stream.Seek(pos + (SkipBits >> 3), SeekOrigin.Begin);
|
||||
SkipBits = SkipBits % 8;
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
107
BDInfo/TSStreamClip.cs
Normal file
107
BDInfo/TSStreamClip.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public class TSStreamClip
|
||||
{
|
||||
public int AngleIndex = 0;
|
||||
public string Name;
|
||||
public double TimeIn;
|
||||
public double TimeOut;
|
||||
public double RelativeTimeIn;
|
||||
public double RelativeTimeOut;
|
||||
public double Length;
|
||||
|
||||
public ulong FileSize = 0;
|
||||
public ulong InterleavedFileSize = 0;
|
||||
public ulong PayloadBytes = 0;
|
||||
public ulong PacketCount = 0;
|
||||
public double PacketSeconds = 0;
|
||||
|
||||
public List<double> Chapters = new List<double>();
|
||||
|
||||
public TSStreamFile StreamFile = null;
|
||||
public TSStreamClipFile StreamClipFile = null;
|
||||
|
||||
public TSStreamClip(
|
||||
TSStreamFile streamFile,
|
||||
TSStreamClipFile streamClipFile)
|
||||
{
|
||||
if (streamFile != null)
|
||||
{
|
||||
Name = streamFile.Name;
|
||||
StreamFile = streamFile;
|
||||
FileSize = (ulong)StreamFile.FileInfo.Length;
|
||||
if (StreamFile.InterleavedFile != null)
|
||||
{
|
||||
InterleavedFileSize = (ulong)StreamFile.InterleavedFile.FileInfo.Length;
|
||||
}
|
||||
}
|
||||
StreamClipFile = streamClipFile;
|
||||
}
|
||||
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (StreamFile != null &&
|
||||
StreamFile.InterleavedFile != null &&
|
||||
BDInfoSettings.EnableSSIF)
|
||||
{
|
||||
return StreamFile.InterleavedFile.Name;
|
||||
}
|
||||
return Name;
|
||||
}
|
||||
}
|
||||
|
||||
public ulong PacketSize => PacketCount * 192;
|
||||
|
||||
public ulong PacketBitRate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (PacketSeconds > 0)
|
||||
{
|
||||
return (ulong)Math.Round(((PacketSize * 8.0) / PacketSeconds));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCompatible(TSStreamClip clip)
|
||||
{
|
||||
foreach (var stream1 in StreamFile.Streams.Values)
|
||||
{
|
||||
if (clip.StreamFile.Streams.ContainsKey(stream1.PID))
|
||||
{
|
||||
var stream2 = clip.StreamFile.Streams[stream1.PID];
|
||||
if (stream1.StreamType != stream2.StreamType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
244
BDInfo/TSStreamClipFile.cs
Normal file
244
BDInfo/TSStreamClipFile.cs
Normal file
@@ -0,0 +1,244 @@
|
||||
//============================================================================
|
||||
// BDInfo - Blu-ray Video and Audio Analysis Tool
|
||||
// Copyright © 2010 Cinema Squid
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2.1 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
//=============================================================================
|
||||
|
||||
#undef DEBUG
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace BDInfo
|
||||
{
|
||||
public class TSStreamClipFile
|
||||
{
|
||||
public FileSystemMetadata FileInfo = null;
|
||||
public string FileType = null;
|
||||
public bool IsValid = false;
|
||||
public string Name = null;
|
||||
|
||||
public Dictionary<ushort, TSStream> Streams =
|
||||
new Dictionary<ushort, TSStream>();
|
||||
|
||||
public TSStreamClipFile(FileSystemMetadata fileInfo)
|
||||
{
|
||||
FileInfo = fileInfo;
|
||||
Name = fileInfo.Name.ToUpper();
|
||||
}
|
||||
|
||||
public void Scan()
|
||||
{
|
||||
Stream fileStream = null;
|
||||
BinaryReader fileReader = null;
|
||||
|
||||
try
|
||||
{
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"Scanning {0}...", Name));
|
||||
#endif
|
||||
Streams.Clear();
|
||||
|
||||
fileStream = File.OpenRead(FileInfo.FullName);
|
||||
fileReader = new BinaryReader(fileStream);
|
||||
|
||||
byte[] data = new byte[fileStream.Length];
|
||||
fileReader.Read(data, 0, data.Length);
|
||||
|
||||
byte[] fileType = new byte[8];
|
||||
Array.Copy(data, 0, fileType, 0, fileType.Length);
|
||||
|
||||
FileType = Encoding.ASCII.GetString(fileType, 0, fileType.Length);
|
||||
if (FileType != "HDMV0100" &&
|
||||
FileType != "HDMV0200")
|
||||
{
|
||||
throw new Exception(string.Format(
|
||||
"Clip info file {0} has an unknown file type {1}.",
|
||||
FileInfo.Name, FileType));
|
||||
}
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\tFileType: {0}", FileType));
|
||||
#endif
|
||||
int clipIndex =
|
||||
((int)data[12] << 24) +
|
||||
((int)data[13] << 16) +
|
||||
((int)data[14] << 8) +
|
||||
((int)data[15]);
|
||||
|
||||
int clipLength =
|
||||
((int)data[clipIndex] << 24) +
|
||||
((int)data[clipIndex + 1] << 16) +
|
||||
((int)data[clipIndex + 2] << 8) +
|
||||
((int)data[clipIndex + 3]);
|
||||
|
||||
byte[] clipData = new byte[clipLength];
|
||||
Array.Copy(data, clipIndex + 4, clipData, 0, clipData.Length);
|
||||
|
||||
int streamCount = clipData[8];
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\tStreamCount: {0}", streamCount));
|
||||
#endif
|
||||
int streamOffset = 10;
|
||||
for (int streamIndex = 0;
|
||||
streamIndex < streamCount;
|
||||
streamIndex++)
|
||||
{
|
||||
TSStream stream = null;
|
||||
|
||||
ushort PID = (ushort)
|
||||
((clipData[streamOffset] << 8) +
|
||||
clipData[streamOffset + 1]);
|
||||
|
||||
streamOffset += 2;
|
||||
|
||||
var streamType = (TSStreamType)
|
||||
clipData[streamOffset + 1];
|
||||
switch (streamType)
|
||||
{
|
||||
case TSStreamType.MVC_VIDEO:
|
||||
// TODO
|
||||
break;
|
||||
|
||||
case TSStreamType.AVC_VIDEO:
|
||||
case TSStreamType.MPEG1_VIDEO:
|
||||
case TSStreamType.MPEG2_VIDEO:
|
||||
case TSStreamType.VC1_VIDEO:
|
||||
{
|
||||
var videoFormat = (TSVideoFormat)
|
||||
(clipData[streamOffset + 2] >> 4);
|
||||
var frameRate = (TSFrameRate)
|
||||
(clipData[streamOffset + 2] & 0xF);
|
||||
var aspectRatio = (TSAspectRatio)
|
||||
(clipData[streamOffset + 3] >> 4);
|
||||
|
||||
stream = new TSVideoStream();
|
||||
((TSVideoStream)stream).VideoFormat = videoFormat;
|
||||
((TSVideoStream)stream).AspectRatio = aspectRatio;
|
||||
((TSVideoStream)stream).FrameRate = frameRate;
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\t{0} {1} {2} {3} {4}",
|
||||
PID,
|
||||
streamType,
|
||||
videoFormat,
|
||||
frameRate,
|
||||
aspectRatio));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case TSStreamType.AC3_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_AUDIO:
|
||||
case TSStreamType.AC3_PLUS_SECONDARY_AUDIO:
|
||||
case TSStreamType.AC3_TRUE_HD_AUDIO:
|
||||
case TSStreamType.DTS_AUDIO:
|
||||
case TSStreamType.DTS_HD_AUDIO:
|
||||
case TSStreamType.DTS_HD_MASTER_AUDIO:
|
||||
case TSStreamType.DTS_HD_SECONDARY_AUDIO:
|
||||
case TSStreamType.LPCM_AUDIO:
|
||||
case TSStreamType.MPEG1_AUDIO:
|
||||
case TSStreamType.MPEG2_AUDIO:
|
||||
{
|
||||
byte[] languageBytes = new byte[3];
|
||||
Array.Copy(clipData, streamOffset + 3,
|
||||
languageBytes, 0, languageBytes.Length);
|
||||
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||
|
||||
var channelLayout = (TSChannelLayout)
|
||||
(clipData[streamOffset + 2] >> 4);
|
||||
var sampleRate = (TSSampleRate)
|
||||
(clipData[streamOffset + 2] & 0xF);
|
||||
|
||||
stream = new TSAudioStream();
|
||||
((TSAudioStream)stream).LanguageCode = languageCode;
|
||||
((TSAudioStream)stream).ChannelLayout = channelLayout;
|
||||
((TSAudioStream)stream).SampleRate = TSAudioStream.ConvertSampleRate(sampleRate);
|
||||
((TSAudioStream)stream).LanguageCode = languageCode;
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\t{0} {1} {2} {3} {4}",
|
||||
PID,
|
||||
streamType,
|
||||
languageCode,
|
||||
channelLayout,
|
||||
sampleRate));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case TSStreamType.INTERACTIVE_GRAPHICS:
|
||||
case TSStreamType.PRESENTATION_GRAPHICS:
|
||||
{
|
||||
byte[] languageBytes = new byte[3];
|
||||
Array.Copy(clipData, streamOffset + 2,
|
||||
languageBytes, 0, languageBytes.Length);
|
||||
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||
|
||||
stream = new TSGraphicsStream();
|
||||
stream.LanguageCode = languageCode;
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\t{0} {1} {2}",
|
||||
PID,
|
||||
streamType,
|
||||
languageCode));
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case TSStreamType.SUBTITLE:
|
||||
{
|
||||
byte[] languageBytes = new byte[3];
|
||||
Array.Copy(clipData, streamOffset + 3,
|
||||
languageBytes, 0, languageBytes.Length);
|
||||
string languageCode = Encoding.ASCII.GetString(languageBytes, 0, languageBytes.Length);
|
||||
#if DEBUG
|
||||
Debug.WriteLine(string.Format(
|
||||
"\t{0} {1} {2}",
|
||||
PID,
|
||||
streamType,
|
||||
languageCode));
|
||||
#endif
|
||||
stream = new TSTextStream();
|
||||
stream.LanguageCode = languageCode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (stream != null)
|
||||
{
|
||||
stream.PID = PID;
|
||||
stream.StreamType = streamType;
|
||||
Streams.Add(PID, stream);
|
||||
}
|
||||
|
||||
streamOffset += clipData[streamOffset] + 1;
|
||||
}
|
||||
IsValid = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (fileReader != null) fileReader.Dispose();
|
||||
if (fileStream != null) fileStream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1555
BDInfo/TSStreamFile.cs
Normal file
1555
BDInfo/TSStreamFile.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
||||
P:System.Threading.Tasks.Task`1.Result
|
||||
M:System.Guid.op_Equality(System.Guid,System.Guid)
|
||||
M:System.Guid.op_Inequality(System.Guid,System.Guid)
|
||||
M:System.Guid.Equals(System.Object)
|
||||
242
CONTRIBUTORS.md
242
CONTRIBUTORS.md
@@ -1,210 +1,35 @@
|
||||
# Jellyfin Contributors
|
||||
|
||||
- [1337joe](https://github.com/1337joe)
|
||||
- [97carmine](https://github.com/97carmine)
|
||||
- [Abbe98](https://github.com/Abbe98)
|
||||
- [agrenott](https://github.com/agrenott)
|
||||
- [alltilla](https://github.com/alltilla)
|
||||
- [AndreCarvalho](https://github.com/AndreCarvalho)
|
||||
- [anthonylavado](https://github.com/anthonylavado)
|
||||
- [Artiume](https://github.com/Artiume)
|
||||
- [AThomsen](https://github.com/AThomsen)
|
||||
- [barongreenback](https://github.com/BaronGreenback)
|
||||
- [barronpm](https://github.com/barronpm)
|
||||
- [bilde2910](https://github.com/bilde2910)
|
||||
- [bfayers](https://github.com/bfayers)
|
||||
- [BnMcG](https://github.com/BnMcG)
|
||||
- [Bond-009](https://github.com/Bond-009)
|
||||
- [brianjmurrell](https://github.com/brianjmurrell)
|
||||
- [bugfixin](https://github.com/bugfixin)
|
||||
- [chaosinnovator](https://github.com/chaosinnovator)
|
||||
- [ckcr4lyf](https://github.com/ckcr4lyf)
|
||||
- [cocool97](https://github.com/cocool97)
|
||||
- [ConfusedPolarBear](https://github.com/ConfusedPolarBear)
|
||||
- [crankdoofus](https://github.com/crankdoofus)
|
||||
- [crobibero](https://github.com/crobibero)
|
||||
- [cromefire](https://github.com/cromefire)
|
||||
- [cryptobank](https://github.com/cryptobank)
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [dannymichel](https://github.com/dannymichel)
|
||||
- [darioackermann](https://github.com/darioackermann)
|
||||
- [DaveChild](https://github.com/DaveChild)
|
||||
- [DavidFair](https://github.com/DavidFair)
|
||||
- [Delgan](https://github.com/Delgan)
|
||||
- [Derpipose](https://github.com/Derpipose)
|
||||
- [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)
|
||||
- [eglia](https://github.com/eglia)
|
||||
- [EgorBakanov](https://github.com/EgorBakanov)
|
||||
- [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)
|
||||
- [GermanCoding](https://github.com/GermanCoding)
|
||||
- [gnattu](https://github.com/gnattu)
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [h1nk](https://github.com/h1nk)
|
||||
- [hawken93](https://github.com/hawken93)
|
||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
||||
- [ikomhoog](https://github.com/ikomhoog)
|
||||
- [iwalton3](https://github.com/iwalton3)
|
||||
- [jftuga](https://github.com/jftuga)
|
||||
- [jkhsjdhjs](https://github.com/jkhsjdhjs)
|
||||
- [jmshrv](https://github.com/jmshrv)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [joshuaboniface](https://github.com/joshuaboniface)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [justinfenn](https://github.com/justinfenn)
|
||||
- [JPVenson](https://github.com/JPVenson)
|
||||
- [KerryRJ](https://github.com/KerryRJ)
|
||||
- [Larvitar](https://github.com/Larvitar)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [lmaonator](https://github.com/lmaonator)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy)
|
||||
- [loli10K](https://github.com/loli10K)
|
||||
- [lostmypillow](https://github.com/lostmypillow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [ManfredRichthofen](https://github.com/ManfredRichthofen)
|
||||
- [Marenz](https://github.com/Marenz)
|
||||
- [marius-luca-87](https://github.com/marius-luca-87)
|
||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
|
||||
- [Matt07211](https://github.com/Matt07211)
|
||||
- [Maxr1998](https://github.com/Maxr1998)
|
||||
- [mcarlton00](https://github.com/mcarlton00)
|
||||
- [mitchfizz05](https://github.com/mitchfizz05)
|
||||
- [mohd-akram](https://github.com/mohd-akram)
|
||||
- [MrTimscampi](https://github.com/MrTimscampi)
|
||||
- [n8225](https://github.com/n8225)
|
||||
- [Nalsai](https://github.com/Nalsai)
|
||||
- [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)
|
||||
- [nicknsy](https://github.com/nicknsy)
|
||||
- [JoshuaBoniface](https://github.com/joshuaboniface)
|
||||
- [nvllsvm](https://github.com/nvllsvm)
|
||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||
- [OancaAndrei](https://github.com/OancaAndrei)
|
||||
- [obradovichv](https://github.com/obradovichv)
|
||||
- [oddstr13](https://github.com/oddstr13)
|
||||
- [orryverducci](https://github.com/orryverducci)
|
||||
- [petermcneil](https://github.com/petermcneil)
|
||||
- [Phlogi](https://github.com/Phlogi)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pR0Ps](https://github.com/pR0Ps)
|
||||
- [PrplHaz4](https://github.com/PrplHaz4)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [redSpoutnik](https://github.com/redSpoutnik)
|
||||
- [ringmatter](https://github.com/ringmatter)
|
||||
- [ryan-hartzell](https://github.com/ryan-hartzell)
|
||||
- [s0urcelab](https://github.com/s0urcelab)
|
||||
- [sachk](https://github.com/sachk)
|
||||
- [sammyrc34](https://github.com/sammyrc34)
|
||||
- [samuel9554](https://github.com/samuel9554)
|
||||
- [scheidleon](https://github.com/scheidleon)
|
||||
- [sebPomme](https://github.com/sebPomme)
|
||||
- [SegiH](https://github.com/SegiH)
|
||||
- [SenorSmartyPants](https://github.com/SenorSmartyPants)
|
||||
- [shemanaev](https://github.com/shemanaev)
|
||||
- [skaro13](https://github.com/skaro13)
|
||||
- [sl1288](https://github.com/sl1288)
|
||||
- [Smith00101010](https://github.com/Smith00101010)
|
||||
- [sorinyo2004](https://github.com/sorinyo2004)
|
||||
- [JustAMan](https://github.com/JustAMan)
|
||||
- [dcrdev](https://github.com/dcrdev)
|
||||
- [EraYaN](https://github.com/EraYaN)
|
||||
- [flemse](https://github.com/flemse)
|
||||
- [bfayers](https://github.com/bfayers)
|
||||
- [Bond_009](https://github.com/Bond-009)
|
||||
- [AnthonyLavado](https://github.com/anthonylavado)
|
||||
- [sparky8251](https://github.com/sparky8251)
|
||||
- [spookbits](https://github.com/spookbits)
|
||||
- [ssenart](https://github.com/ssenart)
|
||||
- [stanionascu](https://github.com/stanionascu)
|
||||
- [stevehayles](https://github.com/stevehayles)
|
||||
- [StollD](https://github.com/StollD)
|
||||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||
- [teacupx](https://github.com/teacupx)
|
||||
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
|
||||
- [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)
|
||||
- [tjwalkr3](https://github.com/tjwalkr3)
|
||||
- [TrisMcC](https://github.com/TrisMcC)
|
||||
- [trumblejoe](https://github.com/trumblejoe)
|
||||
- [TtheCreator](https://github.com/TtheCreator)
|
||||
- [twinkybot](https://github.com/twinkybot)
|
||||
- [Ullmie02](https://github.com/Ullmie02)
|
||||
- [Unhelpful](https://github.com/Unhelpful)
|
||||
- [viaregio](https://github.com/viaregio)
|
||||
- [vitorsemeano](https://github.com/vitorsemeano)
|
||||
- [voodoos](https://github.com/voodoos)
|
||||
- [whooo](https://github.com/whooo)
|
||||
- [WiiPlayer2](https://github.com/WiiPlayer2)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [LeoVerto](https://github.com/LeoVerto)
|
||||
- [grafixeyehero](https://github.com/grafixeyehero)
|
||||
- [cvium](https://github.com/cvium)
|
||||
- [wtayl0r](https://github.com/wtayl0r)
|
||||
- [Wuerfelbecher](https://github.com/Wuerfelbecher)
|
||||
- [Wunax](https://github.com/Wunax)
|
||||
- [WWWesten](https://github.com/WWWesten)
|
||||
- [WX9yMOXWId](https://github.com/WX9yMOXWId)
|
||||
- [xosdy](https://github.com/xosdy)
|
||||
- [XVicarious](https://github.com/XVicarious)
|
||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||
- [ZachPhelan](https://github.com/ZachPhelan)
|
||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||
- [Pusta](https://github.com/pusta)
|
||||
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
||||
- [skyfrk](https://github.com/skyfrk)
|
||||
- [ianjazz246](https://github.com/ianjazz246)
|
||||
- [peterspenler](https://github.com/peterspenler)
|
||||
- [MBR-0001](https://github.com/MBR-0001)
|
||||
- [jonas-resch](https://github.com/jonas-resch)
|
||||
- [vgambier](https://github.com/vgambier)
|
||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||
- [RealGreenDragon](https://github.com/RealGreenDragon)
|
||||
- [ipitio](https://github.com/ipitio)
|
||||
- [TheTyrius](https://github.com/TheTyrius)
|
||||
- [tallbl0nde](https://github.com/tallbl0nde)
|
||||
- [sleepycatcoding](https://github.com/sleepycatcoding)
|
||||
- [scampower3](https://github.com/scampower3)
|
||||
- [Chris-Codes-It](https://github.com/Chris-Codes-It)
|
||||
- [Pithaya](https://github.com/Pithaya)
|
||||
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
|
||||
- [Barasingha](https://github.com/MaVdbussche)
|
||||
- [Gauvino](https://github.com/Gauvino)
|
||||
- [felix920506](https://github.com/felix920506)
|
||||
- [btopherjohnson](https://github.com/btopherjohnson)
|
||||
- [GeorgeH005](https://github.com/GeorgeH005)
|
||||
- [Vedant](https://github.com/viktory36/)
|
||||
- [NotSaifA](https://github.com/NotSaifA)
|
||||
- [HonestlyWhoKnows](https://github.com/honestlywhoknows)
|
||||
- [TheMelmacian](https://github.com/TheMelmacian)
|
||||
- [ItsAllAboutTheCode](https://github.com/ItsAllAboutTheCode)
|
||||
- [pret0rian8](https://github.com/pret0rian)
|
||||
- [jaina heartles](https://github.com/heartles)
|
||||
- [oxixes](https://github.com/oxixes)
|
||||
- [elfalem](https://github.com/elfalem)
|
||||
- [Kenneth Cochran](https://github.com/kennethcochran)
|
||||
- [benedikt257](https://github.com/benedikt257)
|
||||
- [revam](https://github.com/revam)
|
||||
- [allesmi](https://github.com/allesmi)
|
||||
- [ThunderClapLP](https://github.com/ThunderClapLP)
|
||||
- [Shoham Peller](https://github.com/spellr)
|
||||
- [theshoeshiner](https://github.com/theshoeshiner)
|
||||
- [TokerX](https://github.com/TokerX)
|
||||
- [GeneMarks](https://github.com/GeneMarks)
|
||||
- [TtheCreator](https://github.com/Tthecreator)
|
||||
- [dkanada](https://github.com/dkanada)
|
||||
- [LogicalPhallacy](https://github.com/LogicalPhallacy/)
|
||||
- [RazeLighter777](https://github.com/RazeLighter777)
|
||||
- [WillWill56](https://github.com/WillWill56)
|
||||
- [Liggy](https://github.com/Liggy)
|
||||
- [fruhnow](https://github.com/fruhnow)
|
||||
- [Lynxy](https://github.com/Lynxy)
|
||||
- [fasheng](https://github.com/fasheng)
|
||||
- [ploughpuff](https://github.com/ploughpuff)
|
||||
- [pjeanjean](https://github.com/pjeanjean)
|
||||
- [DrPandemic](https://github.com/drpandemic)
|
||||
- [joern-h](https://github.com/joern-h)
|
||||
- [Khinenw](https://github.com/HelloWorld017)
|
||||
- [fhriley](https://github.com/fhriley)
|
||||
- [nevado](https://github.com/nevado)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
@@ -268,16 +93,3 @@
|
||||
- [tikuf](https://github.com/tikuf/)
|
||||
- [Tim Hobbs](https://github.com/timhobbs)
|
||||
- [SvenVandenbrande](https://github.com/SvenVandenbrande)
|
||||
- [olsh](https://github.com/olsh)
|
||||
- [lbenini](https://github.com/lbenini)
|
||||
- [gnuyent](https://github.com/gnuyent)
|
||||
- [Matthew Jones](https://github.com/matthew-jones-uk)
|
||||
- [Jakob Kukla](https://github.com/jakobkukla)
|
||||
- [Utku Özdemir](https://github.com/utkuozdemir)
|
||||
- [JPUC1143](https://github.com/Jpuc1143/)
|
||||
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
|
||||
- [Robert Lützner](https://github.com/rluetzner)
|
||||
- [Nathan McCrina](https://github.com/nfmccrina)
|
||||
- [Martin Reuter](https://github.com/reuterma24)
|
||||
- [Michael McElroy](https://github.com/mcmcelro)
|
||||
- [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<Project>
|
||||
<!-- Sets defaults for all projects in the repo -->
|
||||
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsNotAsErrors>NU1902;NU1903</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/BannedSymbols.txt" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)/stylecop.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Custom Analyzers -->
|
||||
<ItemGroup Condition=" '$(MSBuildProjectName)' != 'Jellyfin.CodeAnalysis' AND '$(Configuration)' == 'Debug' ">
|
||||
<ProjectReference Include="$(MSBuildThisFileDirectory)src/Jellyfin.CodeAnalysis/Jellyfin.CodeAnalysis.csproj" OutputItemType="Analyzer" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,99 +0,0 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||
<ItemGroup Label="Package Dependencies">
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="7.1.7" />
|
||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
||||
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
||||
<PackageVersion Include="BDInfo" Version="0.8.0" />
|
||||
<PackageVersion Include="BitFaster.Caching" Version="2.5.4" />
|
||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
|
||||
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||
<PackageVersion Include="Diacritics" Version="4.0.17" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageVersion Include="FsCheck.Xunit" Version="3.3.1" />
|
||||
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
|
||||
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
||||
<PackageVersion Include="Ignore" Version="0.2.1" />
|
||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageVersion Include="libse" Version="4.0.12" />
|
||||
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="7.0.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.10" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="NEbml" Version="1.1.0.5" />
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageVersion Include="PlaylistsNET" Version="1.4.1" />
|
||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||
<PackageVersion Include="Polly" Version="8.6.4" />
|
||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
|
||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||
<PackageVersion Include="SharpFuzz" Version="2.2.0" />
|
||||
<!-- Pinned to 3.116.1 because https://github.com/jellyfin/jellyfin/pull/14255 -->
|
||||
<PackageVersion Include="SkiaSharp" Version="3.116.1" />
|
||||
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="3.116.1" />
|
||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="3.116.1" />
|
||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="Svg.Skia" Version="3.2.1" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.10" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="7.6.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.3.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
ARG DOTNET_VERSION=2.2
|
||||
ARG FFMPEG_VERSION=latest
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.1
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}
|
||||
COPY --from=ffmpeg / /
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
# Install dependencies:
|
||||
# libfontconfig1: needed for Skia
|
||||
# mesa-va-drivers: needed for VAAPI
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
libfontconfig1 mesa-va-drivers \
|
||||
&& apt-get clean autoclean \
|
||||
&& apt-get autoremove \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/local/bin/ffmpeg
|
||||
44
Dockerfile.arm
Normal file
44
Dockerfile.arm
Normal file
@@ -0,0 +1,44 @@
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.1
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# TODO Remove or update the sed line when we update dotnet version.
|
||||
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm32v7
|
||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
44
Dockerfile.arm64
Normal file
44
Dockerfile.arm64
Normal file
@@ -0,0 +1,44 @@
|
||||
# Requires binfm_misc registration
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=3.0
|
||||
|
||||
|
||||
FROM node:alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=v10.4.1
|
||||
RUN apk add curl \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& cd jellyfin-web-* \
|
||||
&& yarn install \
|
||||
&& yarn build \
|
||||
&& mv dist /dist
|
||||
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# TODO Remove or update the sed line when we update dotnet version.
|
||||
RUN find . -type f -exec sed -i 's/netcoreapp2.1/netcoreapp3.0/g' {} \;
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM mcr.microsoft.com/dotnet/core/runtime:${DOTNET_VERSION}-stretch-slim-arm64v8
|
||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media
|
||||
COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config /media
|
||||
ENTRYPOINT dotnet /jellyfin/jellyfin.dll \
|
||||
--datadir /config \
|
||||
--cachedir /cache \
|
||||
--ffmpeg /usr/bin/ffmpeg
|
||||
30
DvdLib/BigEndianBinaryReader.cs
Normal file
30
DvdLib/BigEndianBinaryReader.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib
|
||||
{
|
||||
public class BigEndianBinaryReader : BinaryReader
|
||||
{
|
||||
public BigEndianBinaryReader(Stream input)
|
||||
: base(input)
|
||||
{
|
||||
}
|
||||
|
||||
public override ushort ReadUInt16()
|
||||
{
|
||||
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
|
||||
}
|
||||
|
||||
public override uint ReadUInt32()
|
||||
{
|
||||
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
|
||||
}
|
||||
|
||||
private byte[] ReadAndReverseBytes(int count)
|
||||
{
|
||||
byte[] val = base.ReadBytes(count);
|
||||
Array.Reverse(val, 0, count);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DvdLib/DvdLib.csproj
Normal file
17
DvdLib/DvdLib.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
20
DvdLib/Ifo/Cell.cs
Normal file
20
DvdLib/Ifo/Cell.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class Cell
|
||||
{
|
||||
public CellPlaybackInfo PlaybackInfo { get; private set; }
|
||||
public CellPositionInfo PositionInfo { get; private set; }
|
||||
|
||||
internal void ParsePlayback(BinaryReader br)
|
||||
{
|
||||
PlaybackInfo = new CellPlaybackInfo(br);
|
||||
}
|
||||
|
||||
internal void ParsePosition(BinaryReader br)
|
||||
{
|
||||
PositionInfo = new CellPositionInfo(br);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
DvdLib/Ifo/CellPlaybackInfo.cs
Normal file
50
DvdLib/Ifo/CellPlaybackInfo.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public enum BlockMode
|
||||
{
|
||||
NotInBlock = 0,
|
||||
FirstCell = 1,
|
||||
InBlock = 2,
|
||||
LastCell = 3,
|
||||
}
|
||||
|
||||
public enum BlockType
|
||||
{
|
||||
Normal = 0,
|
||||
Angle = 1,
|
||||
}
|
||||
|
||||
public enum PlaybackMode
|
||||
{
|
||||
Normal = 0,
|
||||
StillAfterEachVOBU = 1,
|
||||
}
|
||||
|
||||
public class CellPlaybackInfo
|
||||
{
|
||||
public readonly BlockMode Mode;
|
||||
public readonly BlockType Type;
|
||||
public readonly bool SeamlessPlay;
|
||||
public readonly bool Interleaved;
|
||||
public readonly bool STCDiscontinuity;
|
||||
public readonly bool SeamlessAngle;
|
||||
public readonly PlaybackMode PlaybackMode;
|
||||
public readonly bool Restricted;
|
||||
public readonly byte StillTime;
|
||||
public readonly byte CommandNumber;
|
||||
public readonly DvdTime PlaybackTime;
|
||||
public readonly uint FirstSector;
|
||||
public readonly uint FirstILVUEndSector;
|
||||
public readonly uint LastVOBUStartSector;
|
||||
public readonly uint LastSector;
|
||||
|
||||
internal CellPlaybackInfo(BinaryReader br)
|
||||
{
|
||||
br.BaseStream.Seek(0x4, SeekOrigin.Current);
|
||||
PlaybackTime = new DvdTime(br.ReadBytes(4));
|
||||
br.BaseStream.Seek(0x10, SeekOrigin.Current);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
DvdLib/Ifo/CellPositionInfo.cs
Normal file
17
DvdLib/Ifo/CellPositionInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class CellPositionInfo
|
||||
{
|
||||
public readonly ushort VOBId;
|
||||
public readonly byte CellId;
|
||||
|
||||
internal CellPositionInfo(BinaryReader br)
|
||||
{
|
||||
VOBId = br.ReadUInt16();
|
||||
br.ReadByte();
|
||||
CellId = br.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
16
DvdLib/Ifo/Chapter.cs
Normal file
16
DvdLib/Ifo/Chapter.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class Chapter
|
||||
{
|
||||
public ushort ProgramChainNumber { get; private set; }
|
||||
public ushort ProgramNumber { get; private set; }
|
||||
public uint ChapterNumber { get; private set; }
|
||||
|
||||
public Chapter(ushort pgcNum, ushort programNum, uint chapterNum)
|
||||
{
|
||||
ProgramChainNumber = pgcNum;
|
||||
ProgramNumber = programNum;
|
||||
ChapterNumber = chapterNum;
|
||||
}
|
||||
}
|
||||
}
|
||||
158
DvdLib/Ifo/Dvd.cs
Normal file
158
DvdLib/Ifo/Dvd.cs
Normal file
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class Dvd
|
||||
{
|
||||
private readonly ushort _titleSetCount;
|
||||
public readonly List<Title> Titles;
|
||||
|
||||
private ushort _titleCount;
|
||||
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
public Dvd(string path, IFileSystem fileSystem)
|
||||
{
|
||||
_fileSystem = fileSystem;
|
||||
Titles = new List<Title>();
|
||||
var allFiles = _fileSystem.GetFiles(path, true).ToList();
|
||||
|
||||
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (vmgPath == null)
|
||||
{
|
||||
foreach (var ifo in allFiles)
|
||||
{
|
||||
if (!string.Equals(ifo.Extension, ".ifo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
||||
{
|
||||
ReadVTS(ifoNumber, ifo.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
||||
{
|
||||
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
||||
{
|
||||
vmgFs.Seek(0x3E, SeekOrigin.Begin);
|
||||
_titleSetCount = vmgRead.ReadUInt16();
|
||||
|
||||
// read address of TT_SRPT
|
||||
vmgFs.Seek(0xC4, SeekOrigin.Begin);
|
||||
uint ttSectorPtr = vmgRead.ReadUInt32();
|
||||
vmgFs.Seek(ttSectorPtr * 2048, SeekOrigin.Begin);
|
||||
ReadTT_SRPT(vmgRead);
|
||||
}
|
||||
}
|
||||
|
||||
for (ushort titleSetNum = 1; titleSetNum <= _titleSetCount; titleSetNum++)
|
||||
{
|
||||
ReadVTS(titleSetNum, allFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadTT_SRPT(BinaryReader read)
|
||||
{
|
||||
_titleCount = read.ReadUInt16();
|
||||
read.BaseStream.Seek(6, SeekOrigin.Current);
|
||||
for (uint titleNum = 1; titleNum <= _titleCount; titleNum++)
|
||||
{
|
||||
var t = new Title(titleNum);
|
||||
t.ParseTT_SRPT(read);
|
||||
Titles.Add(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
||||
{
|
||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||
|
||||
var vtsPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)) ??
|
||||
allFiles.FirstOrDefault(i => string.Equals(i.Name, Path.ChangeExtension(filename, ".bup"), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (vtsPath == null)
|
||||
{
|
||||
throw new FileNotFoundException("Unable to find VTS IFO file");
|
||||
}
|
||||
|
||||
ReadVTS(vtsNum, vtsPath.FullName);
|
||||
}
|
||||
|
||||
private void ReadVTS(ushort vtsNum, string vtsPath)
|
||||
{
|
||||
VTSPaths[vtsNum] = vtsPath;
|
||||
|
||||
using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
||||
{
|
||||
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
||||
{
|
||||
// Read VTS_PTT_SRPT
|
||||
vtsFs.Seek(0xC8, SeekOrigin.Begin);
|
||||
uint vtsPttSrptSecPtr = vtsRead.ReadUInt32();
|
||||
uint baseAddr = (vtsPttSrptSecPtr * 2048);
|
||||
vtsFs.Seek(baseAddr, SeekOrigin.Begin);
|
||||
|
||||
ushort numTitles = vtsRead.ReadUInt16();
|
||||
vtsRead.ReadUInt16();
|
||||
uint endaddr = vtsRead.ReadUInt32();
|
||||
uint[] offsets = new uint[numTitles];
|
||||
for (ushort titleNum = 0; titleNum < numTitles; titleNum++)
|
||||
{
|
||||
offsets[titleNum] = vtsRead.ReadUInt32();
|
||||
}
|
||||
|
||||
for (uint titleNum = 0; titleNum < numTitles; titleNum++)
|
||||
{
|
||||
uint chapNum = 1;
|
||||
vtsFs.Seek(baseAddr + offsets[titleNum], SeekOrigin.Begin);
|
||||
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum + 1));
|
||||
if (t == null) continue;
|
||||
|
||||
do
|
||||
{
|
||||
t.Chapters.Add(new Chapter(vtsRead.ReadUInt16(), vtsRead.ReadUInt16(), chapNum));
|
||||
if (titleNum + 1 < numTitles && vtsFs.Position == (baseAddr + offsets[titleNum + 1])) break;
|
||||
chapNum++;
|
||||
}
|
||||
while (vtsFs.Position < (baseAddr + endaddr));
|
||||
}
|
||||
|
||||
// Read VTS_PGCI
|
||||
vtsFs.Seek(0xCC, SeekOrigin.Begin);
|
||||
uint vtsPgciSecPtr = vtsRead.ReadUInt32();
|
||||
vtsFs.Seek(vtsPgciSecPtr * 2048, SeekOrigin.Begin);
|
||||
|
||||
long startByte = vtsFs.Position;
|
||||
|
||||
ushort numPgcs = vtsRead.ReadUInt16();
|
||||
vtsFs.Seek(6, SeekOrigin.Current);
|
||||
for (ushort pgcNum = 1; pgcNum <= numPgcs; pgcNum++)
|
||||
{
|
||||
byte pgcCat = vtsRead.ReadByte();
|
||||
bool entryPgc = (pgcCat & 0x80) != 0;
|
||||
uint titleNum = (uint)(pgcCat & 0x7F);
|
||||
|
||||
vtsFs.Seek(3, SeekOrigin.Current);
|
||||
uint vtsPgcOffset = vtsRead.ReadUInt32();
|
||||
|
||||
var t = Titles.FirstOrDefault(vtst => vtst.IsVTSTitle(vtsNum, titleNum));
|
||||
if (t != null) t.AddPgc(vtsRead, startByte + vtsPgcOffset, entryPgc, pgcNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
31
DvdLib/Ifo/DvdTime.cs
Normal file
31
DvdLib/Ifo/DvdTime.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class DvdTime
|
||||
{
|
||||
public readonly byte Hour, Minute, Second, Frames, FrameRate;
|
||||
|
||||
public DvdTime(byte[] data)
|
||||
{
|
||||
Hour = GetBCDValue(data[0]);
|
||||
Minute = GetBCDValue(data[1]);
|
||||
Second = GetBCDValue(data[2]);
|
||||
Frames = GetBCDValue((byte)(data[3] & 0x3F));
|
||||
|
||||
if ((data[3] & 0x80) != 0) FrameRate = 30;
|
||||
else if ((data[3] & 0x40) != 0) FrameRate = 25;
|
||||
}
|
||||
|
||||
private static byte GetBCDValue(byte data)
|
||||
{
|
||||
return (byte)((((data & 0xF0) >> 4) * 10) + (data & 0x0F));
|
||||
}
|
||||
|
||||
public static explicit operator TimeSpan(DvdTime time)
|
||||
{
|
||||
int ms = (int)(((1.0 / (double)time.FrameRate) * time.Frames) * 1000.0);
|
||||
return new TimeSpan(0, time.Hour, time.Minute, time.Second, ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
DvdLib/Ifo/Program.cs
Normal file
14
DvdLib/Ifo/Program.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public readonly List<Cell> Cells;
|
||||
|
||||
public Program(List<Cell> cells)
|
||||
{
|
||||
Cells = cells;
|
||||
}
|
||||
}
|
||||
}
|
||||
108
DvdLib/Ifo/ProgramChain.cs
Normal file
108
DvdLib/Ifo/ProgramChain.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public enum ProgramPlaybackMode
|
||||
{
|
||||
Sequential,
|
||||
Random,
|
||||
Shuffle
|
||||
}
|
||||
|
||||
public class ProgramChain
|
||||
{
|
||||
private byte _programCount;
|
||||
public readonly List<Program> Programs;
|
||||
|
||||
private byte _cellCount;
|
||||
public readonly List<Cell> Cells;
|
||||
|
||||
public DvdTime PlaybackTime { get; private set; }
|
||||
public UserOperation ProhibitedUserOperations { get; private set; }
|
||||
public byte[] AudioStreamControl { get; private set; } // 8*2 entries
|
||||
public byte[] SubpictureStreamControl { get; private set; } // 32*4 entries
|
||||
|
||||
private ushort _nextProgramNumber;
|
||||
|
||||
private ushort _prevProgramNumber;
|
||||
|
||||
private ushort _goupProgramNumber;
|
||||
|
||||
public ProgramPlaybackMode PlaybackMode { get; private set; }
|
||||
public uint ProgramCount { get; private set; }
|
||||
|
||||
public byte StillTime { get; private set; }
|
||||
public byte[] Palette { get; private set; } // 16*4 entries
|
||||
|
||||
private ushort _commandTableOffset;
|
||||
|
||||
private ushort _programMapOffset;
|
||||
private ushort _cellPlaybackOffset;
|
||||
private ushort _cellPositionOffset;
|
||||
|
||||
public readonly uint VideoTitleSetIndex;
|
||||
|
||||
internal ProgramChain(uint vtsPgcNum)
|
||||
{
|
||||
VideoTitleSetIndex = vtsPgcNum;
|
||||
Cells = new List<Cell>();
|
||||
Programs = new List<Program>();
|
||||
}
|
||||
|
||||
internal void ParseHeader(BinaryReader br)
|
||||
{
|
||||
long startPos = br.BaseStream.Position;
|
||||
|
||||
br.ReadUInt16();
|
||||
_programCount = br.ReadByte();
|
||||
_cellCount = br.ReadByte();
|
||||
PlaybackTime = new DvdTime(br.ReadBytes(4));
|
||||
ProhibitedUserOperations = (UserOperation)br.ReadUInt32();
|
||||
AudioStreamControl = br.ReadBytes(16);
|
||||
SubpictureStreamControl = br.ReadBytes(128);
|
||||
|
||||
_nextProgramNumber = br.ReadUInt16();
|
||||
_prevProgramNumber = br.ReadUInt16();
|
||||
_goupProgramNumber = br.ReadUInt16();
|
||||
|
||||
StillTime = br.ReadByte();
|
||||
byte pbMode = br.ReadByte();
|
||||
if (pbMode == 0) PlaybackMode = ProgramPlaybackMode.Sequential;
|
||||
else PlaybackMode = ((pbMode & 0x80) == 0) ? ProgramPlaybackMode.Random : ProgramPlaybackMode.Shuffle;
|
||||
ProgramCount = (uint)(pbMode & 0x7F);
|
||||
|
||||
Palette = br.ReadBytes(64);
|
||||
_commandTableOffset = br.ReadUInt16();
|
||||
_programMapOffset = br.ReadUInt16();
|
||||
_cellPlaybackOffset = br.ReadUInt16();
|
||||
_cellPositionOffset = br.ReadUInt16();
|
||||
|
||||
// read position info
|
||||
br.BaseStream.Seek(startPos + _cellPositionOffset, SeekOrigin.Begin);
|
||||
for (int cellNum = 0; cellNum < _cellCount; cellNum++)
|
||||
{
|
||||
var c = new Cell();
|
||||
c.ParsePosition(br);
|
||||
Cells.Add(c);
|
||||
}
|
||||
|
||||
br.BaseStream.Seek(startPos + _cellPlaybackOffset, SeekOrigin.Begin);
|
||||
for (int cellNum = 0; cellNum < _cellCount; cellNum++)
|
||||
{
|
||||
Cells[cellNum].ParsePlayback(br);
|
||||
}
|
||||
|
||||
br.BaseStream.Seek(startPos + _programMapOffset, SeekOrigin.Begin);
|
||||
var cellNumbers = new List<int>();
|
||||
for (int progNum = 0; progNum < _programCount; progNum++) cellNumbers.Add(br.ReadByte() - 1);
|
||||
|
||||
for (int i = 0; i < cellNumbers.Count; i++)
|
||||
{
|
||||
int max = (i + 1 == cellNumbers.Count) ? _cellCount : cellNumbers[i + 1];
|
||||
Programs.Add(new Program(Cells.Where((c, idx) => idx >= cellNumbers[i] && idx < max).ToList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
61
DvdLib/Ifo/Title.cs
Normal file
61
DvdLib/Ifo/Title.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
public class Title
|
||||
{
|
||||
public uint TitleNumber { get; private set; }
|
||||
public uint AngleCount { get; private set; }
|
||||
public ushort ChapterCount { get; private set; }
|
||||
public byte VideoTitleSetNumber { get; private set; }
|
||||
|
||||
private ushort _parentalManagementMask;
|
||||
private byte _titleNumberInVTS;
|
||||
private uint _vtsStartSector; // relative to start of entire disk
|
||||
|
||||
public ProgramChain EntryProgramChain { get; private set; }
|
||||
public readonly List<ProgramChain> ProgramChains;
|
||||
|
||||
public readonly List<Chapter> Chapters;
|
||||
|
||||
public Title(uint titleNum)
|
||||
{
|
||||
ProgramChains = new List<ProgramChain>();
|
||||
Chapters = new List<Chapter>();
|
||||
Chapters = new List<Chapter>();
|
||||
TitleNumber = titleNum;
|
||||
}
|
||||
|
||||
public bool IsVTSTitle(uint vtsNum, uint vtsTitleNum)
|
||||
{
|
||||
return (vtsNum == VideoTitleSetNumber && vtsTitleNum == _titleNumberInVTS);
|
||||
}
|
||||
|
||||
internal void ParseTT_SRPT(BinaryReader br)
|
||||
{
|
||||
byte titleType = br.ReadByte();
|
||||
// TODO parse Title Type
|
||||
|
||||
AngleCount = br.ReadByte();
|
||||
ChapterCount = br.ReadUInt16();
|
||||
_parentalManagementMask = br.ReadUInt16();
|
||||
VideoTitleSetNumber = br.ReadByte();
|
||||
_titleNumberInVTS = br.ReadByte();
|
||||
_vtsStartSector = br.ReadUInt32();
|
||||
}
|
||||
|
||||
internal void AddPgc(BinaryReader br, long startByte, bool entryPgc, uint pgcNum)
|
||||
{
|
||||
long curPos = br.BaseStream.Position;
|
||||
br.BaseStream.Seek(startByte, SeekOrigin.Begin);
|
||||
|
||||
var pgc = new ProgramChain(pgcNum);
|
||||
pgc.ParseHeader(br);
|
||||
ProgramChains.Add(pgc);
|
||||
if (entryPgc) EntryProgramChain = pgc;
|
||||
|
||||
br.BaseStream.Seek(curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
DvdLib/Ifo/UserOperation.cs
Normal file
35
DvdLib/Ifo/UserOperation.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace DvdLib.Ifo
|
||||
{
|
||||
[Flags]
|
||||
public enum UserOperation
|
||||
{
|
||||
None = 0,
|
||||
TitleOrTimePlay = 1,
|
||||
ChapterSearchOrPlay = 2,
|
||||
TitlePlay = 4,
|
||||
Stop = 8,
|
||||
GoUp = 16,
|
||||
TimeOrChapterSearch = 32,
|
||||
PrevOrTopProgramSearch = 64,
|
||||
NextProgramSearch = 128,
|
||||
ForwardScan = 256,
|
||||
BackwardScan = 512,
|
||||
TitleMenuCall = 1024,
|
||||
RootMenuCall = 2048,
|
||||
SubpictureMenuCall = 4096,
|
||||
AudioMenuCall = 8192,
|
||||
AngleMenuCall = 16384,
|
||||
ChapterMenuCall = 32768,
|
||||
Resume = 65536,
|
||||
ButtonSelectOrActive = 131072,
|
||||
StillOff = 262144,
|
||||
PauseOn = 524288,
|
||||
AudioStreamChange = 1048576,
|
||||
SubpictureStreamChange = 2097152,
|
||||
AngleChange = 4194304,
|
||||
KaraokeAudioPresentationModeChange = 8388608,
|
||||
VideoPresentationModeChange = 16777216,
|
||||
}
|
||||
}
|
||||
21
DvdLib/Properties/AssemblyInfo.cs
Normal file
21
DvdLib/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Reflection;
|
||||
using System.Resources;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("DvdLib")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Jellyfin Project")]
|
||||
[assembly: AssemblyProduct("Jellyfin Server")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// 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)]
|
||||
306
Emby.Dlna/Api/DlnaServerService.cs
Normal file
306
Emby.Dlna/Api/DlnaServerService.cs
Normal file
@@ -0,0 +1,306 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Main;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace Emby.Dlna.Api
|
||||
{
|
||||
[Route("/Dlna/{UuId}/description.xml", "GET", Summary = "Gets dlna server info")]
|
||||
[Route("/Dlna/{UuId}/description", "GET", Summary = "Gets dlna server info")]
|
||||
public class GetDescriptionXml
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/contentdirectory/contentdirectory.xml", "GET", Summary = "Gets dlna content directory xml")]
|
||||
[Route("/Dlna/{UuId}/contentdirectory/contentdirectory", "GET", Summary = "Gets dlna content directory xml")]
|
||||
public class GetContentDirectory
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/connectionmanager/connectionmanager.xml", "GET", Summary = "Gets dlna connection manager xml")]
|
||||
[Route("/Dlna/{UuId}/connectionmanager/connectionmanager", "GET", Summary = "Gets dlna connection manager xml")]
|
||||
public class GetConnnectionManager
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar.xml", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
|
||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/mediareceiverregistrar", "GET", Summary = "Gets dlna mediareceiverregistrar xml")]
|
||||
public class GetMediaReceiverRegistrar
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/contentdirectory/control", "POST", Summary = "Processes a control request")]
|
||||
public class ProcessContentDirectoryControlRequest : IRequiresRequestStream
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
|
||||
public Stream RequestStream { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/connectionmanager/control", "POST", Summary = "Processes a control request")]
|
||||
public class ProcessConnectionManagerControlRequest : IRequiresRequestStream
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
|
||||
public Stream RequestStream { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/control", "POST", Summary = "Processes a control request")]
|
||||
public class ProcessMediaReceiverRegistrarControlRequest : IRequiresRequestStream
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
|
||||
public Stream RequestStream { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
[Route("/Dlna/{UuId}/mediareceiverregistrar/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
public class ProcessMediaReceiverRegistrarEventRequest
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/contentdirectory/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
[Route("/Dlna/{UuId}/contentdirectory/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
public class ProcessContentDirectoryEventRequest
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/connectionmanager/events", "SUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
[Route("/Dlna/{UuId}/connectionmanager/events", "UNSUBSCRIBE", Summary = "Processes an event subscription request")]
|
||||
public class ProcessConnectionManagerEventRequest
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "SUBSCRIBE,UNSUBSCRIBE")]
|
||||
public string UuId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/{UuId}/icons/{Filename}", "GET", Summary = "Gets a server icon")]
|
||||
[Route("/Dlna/icons/{Filename}", "GET", Summary = "Gets a server icon")]
|
||||
public class GetIcon
|
||||
{
|
||||
[ApiMember(Name = "UuId", Description = "Server UuId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string UuId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Filename", Description = "The icon filename", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string Filename { get; set; }
|
||||
}
|
||||
|
||||
public class DlnaServerService : IService, IRequiresRequest
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
|
||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
||||
|
||||
public IRequest Request { get; set; }
|
||||
private IHttpResultFactory _resultFactory;
|
||||
|
||||
private IContentDirectory ContentDirectory => DlnaEntryPoint.Current.ContentDirectory;
|
||||
|
||||
private IConnectionManager ConnectionManager => DlnaEntryPoint.Current.ConnectionManager;
|
||||
|
||||
private IMediaReceiverRegistrar MediaReceiverRegistrar => DlnaEntryPoint.Current.MediaReceiverRegistrar;
|
||||
|
||||
public DlnaServerService(IDlnaManager dlnaManager, IHttpResultFactory httpResultFactory)
|
||||
{
|
||||
_dlnaManager = dlnaManager;
|
||||
_resultFactory = httpResultFactory;
|
||||
}
|
||||
|
||||
private string GetHeader(string name)
|
||||
{
|
||||
return Request.Headers[name];
|
||||
}
|
||||
|
||||
public object Get(GetDescriptionXml request)
|
||||
{
|
||||
var url = Request.AbsoluteUri;
|
||||
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
|
||||
var xml = _dlnaManager.GetServerDescriptionXml(Request.Headers, request.UuId, serverAddress);
|
||||
|
||||
var cacheLength = TimeSpan.FromDays(1);
|
||||
var cacheKey = Request.RawUrl.GetMD5();
|
||||
var bytes = Encoding.UTF8.GetBytes(xml);
|
||||
|
||||
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
|
||||
}
|
||||
|
||||
public object Get(GetContentDirectory request)
|
||||
{
|
||||
var xml = ContentDirectory.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Get(GetMediaReceiverRegistrar request)
|
||||
{
|
||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Get(GetConnnectionManager request)
|
||||
{
|
||||
var xml = ConnectionManager.GetServiceXml();
|
||||
|
||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessMediaReceiverRegistrarControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessContentDirectoryControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, ContentDirectory);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
public object Post(ProcessConnectionManagerControlRequest request)
|
||||
{
|
||||
var response = PostAsync(request.RequestStream, ConnectionManager);
|
||||
|
||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||
}
|
||||
|
||||
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
|
||||
{
|
||||
var id = GetPathValue(2);
|
||||
|
||||
return service.ProcessControlRequest(new ControlRequest
|
||||
{
|
||||
Headers = Request.Headers,
|
||||
InputXml = requestStream,
|
||||
TargetServerUuId = id,
|
||||
RequestedUrl = Request.AbsoluteUri
|
||||
});
|
||||
}
|
||||
|
||||
protected string GetPathValue(int index)
|
||||
{
|
||||
var pathInfo = Parse(Request.PathInfo);
|
||||
var first = pathInfo[0];
|
||||
|
||||
// backwards compatibility
|
||||
// TODO: Work out what this is doing.
|
||||
if (string.Equals(first, "mediabrowser", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(first, "emby", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(first, "jellyfin", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
index++;
|
||||
}
|
||||
|
||||
return pathInfo[index];
|
||||
}
|
||||
|
||||
private List<string> Parse(string pathUri)
|
||||
{
|
||||
var actionParts = pathUri.Split(new[] { "://" }, StringSplitOptions.None);
|
||||
|
||||
var pathInfo = actionParts[actionParts.Length - 1];
|
||||
|
||||
var optionsPos = pathInfo.LastIndexOf('?');
|
||||
if (optionsPos != -1)
|
||||
{
|
||||
pathInfo = pathInfo.Substring(0, optionsPos);
|
||||
}
|
||||
|
||||
var args = pathInfo.Split('/');
|
||||
|
||||
return args.Skip(1).ToList();
|
||||
}
|
||||
|
||||
public object Get(GetIcon request)
|
||||
{
|
||||
var contentType = "image/" + Path.GetExtension(request.Filename)
|
||||
.TrimStart('.')
|
||||
.ToLowerInvariant();
|
||||
|
||||
var cacheLength = TimeSpan.FromDays(365);
|
||||
var cacheKey = Request.RawUrl.GetMD5();
|
||||
|
||||
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
|
||||
}
|
||||
|
||||
public object Subscribe(ProcessContentDirectoryEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ContentDirectory);
|
||||
}
|
||||
|
||||
public object Subscribe(ProcessConnectionManagerEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ConnectionManager);
|
||||
}
|
||||
|
||||
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||
}
|
||||
|
||||
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ContentDirectory);
|
||||
}
|
||||
|
||||
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(ConnectionManager);
|
||||
}
|
||||
|
||||
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||
{
|
||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||
}
|
||||
|
||||
private object ProcessEventRequest(IEventManager eventManager)
|
||||
{
|
||||
var subscriptionId = GetHeader("SID");
|
||||
|
||||
if (string.Equals(Request.Verb, "SUBSCRIBE", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var notificationType = GetHeader("NT");
|
||||
|
||||
var callback = GetHeader("CALLBACK");
|
||||
var timeoutString = GetHeader("TIMEOUT");
|
||||
|
||||
if (string.IsNullOrEmpty(notificationType))
|
||||
{
|
||||
return GetSubscriptionResponse(eventManager.RenewEventSubscription(subscriptionId, notificationType, timeoutString, callback));
|
||||
}
|
||||
|
||||
return GetSubscriptionResponse(eventManager.CreateEventSubscription(notificationType, timeoutString, callback));
|
||||
}
|
||||
|
||||
return GetSubscriptionResponse(eventManager.CancelEventSubscription(subscriptionId));
|
||||
}
|
||||
|
||||
private object GetSubscriptionResponse(EventSubscriptionResponse response)
|
||||
{
|
||||
return _resultFactory.GetResult(Request, response.Content, response.ContentType, response.Headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
Emby.Dlna/Api/DlnaService.cs
Normal file
83
Emby.Dlna/Api/DlnaService.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Linq;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace Emby.Dlna.Api
|
||||
{
|
||||
[Route("/Dlna/ProfileInfos", "GET", Summary = "Gets a list of profiles")]
|
||||
public class GetProfileInfos : IReturn<DeviceProfileInfo[]>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Dlna/Profiles/{Id}", "DELETE", Summary = "Deletes a profile")]
|
||||
public class DeleteProfile : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/Profiles/Default", "GET", Summary = "Gets the default profile")]
|
||||
public class GetDefaultProfile : IReturn<DeviceProfile>
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Dlna/Profiles/{Id}", "GET", Summary = "Gets a single profile")]
|
||||
public class GetProfile : IReturn<DeviceProfile>
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Profile Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Dlna/Profiles/{Id}", "POST", Summary = "Updates a profile")]
|
||||
public class UpdateProfile : DeviceProfile, IReturnVoid
|
||||
{
|
||||
}
|
||||
|
||||
[Route("/Dlna/Profiles", "POST", Summary = "Creates a profile")]
|
||||
public class CreateProfile : DeviceProfile, IReturnVoid
|
||||
{
|
||||
}
|
||||
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class DlnaService : IService
|
||||
{
|
||||
private readonly IDlnaManager _dlnaManager;
|
||||
|
||||
public DlnaService(IDlnaManager dlnaManager)
|
||||
{
|
||||
_dlnaManager = dlnaManager;
|
||||
}
|
||||
|
||||
public object Get(GetProfileInfos request)
|
||||
{
|
||||
return _dlnaManager.GetProfileInfos().ToArray();
|
||||
}
|
||||
|
||||
public object Get(GetProfile request)
|
||||
{
|
||||
return _dlnaManager.GetProfile(request.Id);
|
||||
}
|
||||
|
||||
public object Get(GetDefaultProfile request)
|
||||
{
|
||||
return _dlnaManager.GetDefaultProfile();
|
||||
}
|
||||
|
||||
public void Delete(DeleteProfile request)
|
||||
{
|
||||
_dlnaManager.DeleteProfile(request.Id);
|
||||
}
|
||||
|
||||
public void Post(UpdateProfile request)
|
||||
{
|
||||
_dlnaManager.UpdateProfile(request);
|
||||
}
|
||||
|
||||
public void Post(CreateProfile request)
|
||||
{
|
||||
_dlnaManager.CreateProfile(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Emby.Dlna/Common/Argument.cs
Normal file
12
Emby.Dlna/Common/Argument.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class Argument
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string Direction { get; set; }
|
||||
|
||||
public string RelatedStateVariable { get; set; }
|
||||
}
|
||||
}
|
||||
21
Emby.Dlna/Common/DeviceIcon.cs
Normal file
21
Emby.Dlna/Common/DeviceIcon.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class DeviceIcon
|
||||
{
|
||||
public string Url { get; set; }
|
||||
|
||||
public string MimeType { get; set; }
|
||||
|
||||
public int Width { get; set; }
|
||||
|
||||
public int Height { get; set; }
|
||||
|
||||
public string Depth { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}x{1}", Height, Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Emby.Dlna/Common/DeviceService.cs
Normal file
21
Emby.Dlna/Common/DeviceService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class DeviceService
|
||||
{
|
||||
public string ServiceType { get; set; }
|
||||
|
||||
public string ServiceId { get; set; }
|
||||
|
||||
public string ScpdUrl { get; set; }
|
||||
|
||||
public string ControlUrl { get; set; }
|
||||
|
||||
public string EventSubUrl { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}", ServiceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Emby.Dlna/Common/ServiceAction.cs
Normal file
21
Emby.Dlna/Common/ServiceAction.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class ServiceAction
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public List<Argument> ArgumentList { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public ServiceAction()
|
||||
{
|
||||
ArgumentList = new List<Argument>();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Emby.Dlna/Common/StateVariable.cs
Normal file
25
Emby.Dlna/Common/StateVariable.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Emby.Dlna.Common
|
||||
{
|
||||
public class StateVariable
|
||||
{
|
||||
public string Name { get; set; }
|
||||
|
||||
public string DataType { get; set; }
|
||||
|
||||
public bool SendsEvents { get; set; }
|
||||
|
||||
public string[] AllowedValues { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public StateVariable()
|
||||
{
|
||||
AllowedValues = Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Emby.Dlna/Configuration/DlnaOptions.cs
Normal file
25
Emby.Dlna/Configuration/DlnaOptions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
namespace Emby.Dlna.Configuration
|
||||
{
|
||||
public class DlnaOptions
|
||||
{
|
||||
public bool EnablePlayTo { get; set; }
|
||||
public bool EnableServer { get; set; }
|
||||
public bool EnableDebugLog { get; set; }
|
||||
public bool BlastAliveMessages { get; set; }
|
||||
public bool SendOnlyMatchedHost { get; set; }
|
||||
public int ClientDiscoveryIntervalSeconds { get; set; }
|
||||
public int BlastAliveMessageIntervalSeconds { get; set; }
|
||||
public string DefaultUserId { get; set; }
|
||||
|
||||
public DlnaOptions()
|
||||
{
|
||||
EnablePlayTo = true;
|
||||
EnableServer = true;
|
||||
BlastAliveMessages = true;
|
||||
SendOnlyMatchedHost = true;
|
||||
ClientDiscoveryIntervalSeconds = 60;
|
||||
BlastAliveMessageIntervalSeconds = 1800;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Emby.Dlna/ConfigurationExtension.cs
Normal file
29
Emby.Dlna/ConfigurationExtension.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Configuration;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public static class ConfigurationExtension
|
||||
{
|
||||
public static DlnaOptions GetDlnaConfiguration(this IConfigurationManager manager)
|
||||
{
|
||||
return manager.GetConfiguration<DlnaOptions>("dlna");
|
||||
}
|
||||
}
|
||||
|
||||
public class DlnaConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new ConfigurationStore[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
Key = "dlna",
|
||||
ConfigurationType = typeof (DlnaOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Emby.Dlna/ConnectionManager/ConnectionManager.cs
Normal file
36
Emby.Dlna/ConnectionManager/ConnectionManager.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
{
|
||||
public class ConnectionManager : BaseService, IConnectionManager
|
||||
{
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
|
||||
public ConnectionManager(IDlnaManager dlna, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ConnectionManagerXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
Normal file
106
Emby.Dlna/ConnectionManager/ConnectionManagerXmlBuilder.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Service;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
{
|
||||
public class ConnectionManagerXmlBuilder
|
||||
{
|
||||
public string GetXml()
|
||||
{
|
||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(), GetStateVariables());
|
||||
}
|
||||
|
||||
private static IEnumerable<StateVariable> GetStateVariables()
|
||||
{
|
||||
var list = new List<StateVariable>();
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SourceProtocolInfo",
|
||||
DataType = "string",
|
||||
SendsEvents = true
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SinkProtocolInfo",
|
||||
DataType = "string",
|
||||
SendsEvents = true
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "CurrentConnectionIDs",
|
||||
DataType = "string",
|
||||
SendsEvents = true
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ConnectionStatus",
|
||||
DataType = "string",
|
||||
SendsEvents = false,
|
||||
|
||||
AllowedValues = new string[]
|
||||
{
|
||||
"OK",
|
||||
"ContentFormatMismatch",
|
||||
"InsufficientBandwidth",
|
||||
"UnreliableChannel",
|
||||
"Unknown"
|
||||
}
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ConnectionManager",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Direction",
|
||||
DataType = "string",
|
||||
SendsEvents = false,
|
||||
|
||||
AllowedValues = new string[]
|
||||
{
|
||||
"Output",
|
||||
"Input"
|
||||
}
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ProtocolInfo",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ConnectionID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_AVTransportID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_RcsID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Emby.Dlna/ConnectionManager/ControlHandler.cs
Normal file
40
Emby.Dlna/ConnectionManager/ControlHandler.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
{
|
||||
public class ControlHandler : BaseControlHandler
|
||||
{
|
||||
private readonly DeviceProfile _profile;
|
||||
|
||||
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||
{
|
||||
if (string.Equals(methodName, "GetProtocolInfo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return HandleGetProtocolInfo();
|
||||
}
|
||||
|
||||
throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
|
||||
}
|
||||
|
||||
private IEnumerable<KeyValuePair<string, string>> HandleGetProtocolInfo()
|
||||
{
|
||||
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
{ "Source", _profile.ProtocolInfo },
|
||||
{ "Sink", "" }
|
||||
};
|
||||
}
|
||||
|
||||
public ControlHandler(IServerConfigurationManager config, ILogger logger, DeviceProfile profile)
|
||||
: base(config, logger)
|
||||
{
|
||||
_profile = profile;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
Normal file
205
Emby.Dlna/ConnectionManager/ServiceActionListBuilder.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
|
||||
namespace Emby.Dlna.ConnectionManager
|
||||
{
|
||||
public class ServiceActionListBuilder
|
||||
{
|
||||
public IEnumerable<ServiceAction> GetActions()
|
||||
{
|
||||
var list = new List<ServiceAction>
|
||||
{
|
||||
GetCurrentConnectionInfo(),
|
||||
GetProtocolInfo(),
|
||||
GetCurrentConnectionIDs(),
|
||||
ConnectionComplete(),
|
||||
PrepareForConnection()
|
||||
};
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static ServiceAction PrepareForConnection()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "PrepareForConnection"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RemoteProtocolInfo",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PeerConnectionManager",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PeerConnectionID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Direction",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Direction"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ConnectionID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "AVTransportID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RcsID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_RcsID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static ServiceAction GetCurrentConnectionInfo()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetCurrentConnectionInfo"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ConnectionID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RcsID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_RcsID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "AVTransportID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_AVTransportID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ProtocolInfo",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ProtocolInfo"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PeerConnectionManager",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionManager"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PeerConnectionID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Direction",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Direction"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Status",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionStatus"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetProtocolInfo()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetProtocolInfo"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Source",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SourceProtocolInfo"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Sink",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SinkProtocolInfo"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetCurrentConnectionIDs()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetCurrentConnectionIDs"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ConnectionIDs",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "CurrentConnectionIDs"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction ConnectionComplete()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "ConnectionComplete"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ConnectionID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ConnectionID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
143
Emby.Dlna/ContentDirectory/ContentDirectory.cs
Normal file
143
Emby.Dlna/ContentDirectory/ContentDirectory.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Service;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
public class ContentDirectory : BaseService, IContentDirectory
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IImageProcessor _imageProcessor;
|
||||
private readonly IUserDataManager _userDataManager;
|
||||
private readonly IDlnaManager _dlna;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IUserViewManager _userViewManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
|
||||
public ContentDirectory(IDlnaManager dlna,
|
||||
IUserDataManager userDataManager,
|
||||
IImageProcessor imageProcessor,
|
||||
ILibraryManager libraryManager,
|
||||
IServerConfigurationManager config,
|
||||
IUserManager userManager,
|
||||
ILogger logger,
|
||||
IHttpClient httpClient,
|
||||
ILocalizationManager localization,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IUserViewManager userViewManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
ITVSeriesManager tvSeriesManager)
|
||||
: base(logger, httpClient)
|
||||
{
|
||||
_dlna = dlna;
|
||||
_userDataManager = userDataManager;
|
||||
_imageProcessor = imageProcessor;
|
||||
_libraryManager = libraryManager;
|
||||
_config = config;
|
||||
_userManager = userManager;
|
||||
_localization = localization;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_userViewManager = userViewManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
}
|
||||
|
||||
private int SystemUpdateId
|
||||
{
|
||||
get
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return now.Year + now.DayOfYear + now.Hour;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetServiceXml()
|
||||
{
|
||||
return new ContentDirectoryXmlBuilder().GetXml();
|
||||
}
|
||||
|
||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
||||
{
|
||||
var profile = _dlna.GetProfile(request.Headers) ??
|
||||
_dlna.GetDefaultProfile();
|
||||
|
||||
var serverAddress = request.RequestedUrl.Substring(0, request.RequestedUrl.IndexOf("/dlna", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var user = GetUser(profile);
|
||||
|
||||
return new ControlHandler(
|
||||
Logger,
|
||||
_libraryManager,
|
||||
profile,
|
||||
serverAddress,
|
||||
null,
|
||||
_imageProcessor,
|
||||
_userDataManager,
|
||||
user,
|
||||
SystemUpdateId,
|
||||
_config,
|
||||
_localization,
|
||||
_mediaSourceManager,
|
||||
_userViewManager,
|
||||
_mediaEncoder,
|
||||
_tvSeriesManager)
|
||||
.ProcessControlRequest(request);
|
||||
}
|
||||
|
||||
private User GetUser(DeviceProfile profile)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profile.UserId))
|
||||
{
|
||||
var user = _userManager.GetUserById(profile.UserId);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
var userId = _config.GetDlnaConfiguration().DefaultUserId;
|
||||
|
||||
if (!string.IsNullOrEmpty(userId))
|
||||
{
|
||||
var user = _userManager.GetUserById(userId);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
if (user.Policy.IsAdministrator)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var user in _userManager.Users)
|
||||
{
|
||||
return user;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
147
Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
Normal file
147
Emby.Dlna/ContentDirectory/ContentDirectoryXmlBuilder.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
using Emby.Dlna.Service;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
public class ContentDirectoryXmlBuilder
|
||||
{
|
||||
public string GetXml()
|
||||
{
|
||||
return new ServiceXmlBuilder().GetXml(new ServiceActionListBuilder().GetActions(),
|
||||
GetStateVariables());
|
||||
}
|
||||
|
||||
private static IEnumerable<StateVariable> GetStateVariables()
|
||||
{
|
||||
var list = new List<StateVariable>();
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Filter",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_SortCriteria",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Index",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Count",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_UpdateID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SearchCapabilities",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SortCapabilities",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "SystemUpdateID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = true
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_SearchCriteria",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Result",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_ObjectID",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_BrowseFlag",
|
||||
DataType = "string",
|
||||
SendsEvents = false,
|
||||
|
||||
AllowedValues = new string[]
|
||||
{
|
||||
"BrowseMetadata",
|
||||
"BrowseDirectChildren"
|
||||
}
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_BrowseLetter",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_CategoryType",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_RID",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_PosSec",
|
||||
DataType = "ui4",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
list.Add(new StateVariable
|
||||
{
|
||||
Name = "A_ARG_TYPE_Featurelist",
|
||||
DataType = "string",
|
||||
SendsEvents = false
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
1382
Emby.Dlna/ContentDirectory/ControlHandler.cs
Normal file
1382
Emby.Dlna/ContentDirectory/ControlHandler.cs
Normal file
File diff suppressed because it is too large
Load Diff
376
Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
Normal file
376
Emby.Dlna/ContentDirectory/ServiceActionListBuilder.cs
Normal file
@@ -0,0 +1,376 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Dlna.Common;
|
||||
|
||||
namespace Emby.Dlna.ContentDirectory
|
||||
{
|
||||
public class ServiceActionListBuilder
|
||||
{
|
||||
public IEnumerable<ServiceAction> GetActions()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
GetSearchCapabilitiesAction(),
|
||||
GetSortCapabilitiesAction(),
|
||||
GetGetSystemUpdateIDAction(),
|
||||
GetBrowseAction(),
|
||||
GetSearchAction(),
|
||||
GetX_GetFeatureListAction(),
|
||||
GetXSetBookmarkAction(),
|
||||
GetBrowseByLetterAction()
|
||||
};
|
||||
}
|
||||
|
||||
private static ServiceAction GetGetSystemUpdateIDAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSystemUpdateID"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Id",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SystemUpdateID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static ServiceAction GetSearchCapabilitiesAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSearchCapabilities"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SearchCaps",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SearchCapabilities"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static ServiceAction GetSortCapabilitiesAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "GetSortCapabilities"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCaps",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "SortCapabilities"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static ServiceAction GetX_GetFeatureListAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "X_GetFeatureList"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "FeatureList",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Featurelist"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static ServiceAction GetSearchAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "Search"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ContainerID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SearchCriteria",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_SearchCriteria"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Filter",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Filter"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "StartingIndex",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Index"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RequestedCount",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCriteria",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Result",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Result"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "NumberReturned",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "TotalMatches",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "UpdateID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetBrowseAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "Browse"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ObjectID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "BrowseFlag",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_BrowseFlag"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Filter",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Filter"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "StartingIndex",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Index"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RequestedCount",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCriteria",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Result",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Result"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "NumberReturned",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "TotalMatches",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "UpdateID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetBrowseByLetterAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "X_BrowseByLetter"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ObjectID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "BrowseFlag",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_BrowseFlag"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Filter",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Filter"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "StartingLetter",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_BrowseLetter"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RequestedCount",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "SortCriteria",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_SortCriteria"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "Result",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Result"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "NumberReturned",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "TotalMatches",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Count"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "UpdateID",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_UpdateID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "StartingIndex",
|
||||
Direction = "out",
|
||||
RelatedStateVariable = "A_ARG_TYPE_Index"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private ServiceAction GetXSetBookmarkAction()
|
||||
{
|
||||
var action = new ServiceAction
|
||||
{
|
||||
Name = "X_SetBookmark"
|
||||
};
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "CategoryType",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_CategoryType"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "RID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_RID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "ObjectID",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_ObjectID"
|
||||
});
|
||||
|
||||
action.ArgumentList.Add(new Argument
|
||||
{
|
||||
Name = "PosSecond",
|
||||
Direction = "in",
|
||||
RelatedStateVariable = "A_ARG_TYPE_PosSec"
|
||||
});
|
||||
|
||||
return action;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Emby.Dlna/ControlRequest.cs
Normal file
21
Emby.Dlna/ControlRequest.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.IO;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class ControlRequest
|
||||
{
|
||||
public IHeaderDictionary Headers { get; set; }
|
||||
|
||||
public Stream InputXml { get; set; }
|
||||
|
||||
public string TargetServerUuId { get; set; }
|
||||
|
||||
public string RequestedUrl { get; set; }
|
||||
|
||||
public ControlRequest()
|
||||
{
|
||||
Headers = new HeaderDictionary();
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Emby.Dlna/ControlResponse.cs
Normal file
18
Emby.Dlna/ControlResponse.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class ControlResponse
|
||||
{
|
||||
public IDictionary<string, string> Headers { get; set; }
|
||||
|
||||
public string Xml { get; set; }
|
||||
|
||||
public bool IsSuccessful { get; set; }
|
||||
|
||||
public ControlResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
1140
Emby.Dlna/Didl/DidlBuilder.cs
Normal file
1140
Emby.Dlna/Didl/DidlBuilder.cs
Normal file
File diff suppressed because it is too large
Load Diff
31
Emby.Dlna/Didl/Filter.cs
Normal file
31
Emby.Dlna/Didl/Filter.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace Emby.Dlna.Didl
|
||||
{
|
||||
public class Filter
|
||||
{
|
||||
private readonly string[] _fields;
|
||||
private readonly bool _all;
|
||||
|
||||
public Filter()
|
||||
: this("*")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Filter(string filter)
|
||||
{
|
||||
_all = StringHelper.EqualsIgnoreCase(filter, "*");
|
||||
|
||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public bool Contains(string field)
|
||||
{
|
||||
// Don't bother with this. Some clients (media monkey) use the filter and then don't display very well when very little data comes back.
|
||||
return true;
|
||||
//return _all || ListHelper.ContainsIgnoreCase(_fields, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Emby.Dlna/Didl/StringWriterWithEncoding.cs
Normal file
56
Emby.Dlna/Didl/StringWriterWithEncoding.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Emby.Dlna.Didl
|
||||
{
|
||||
public class StringWriterWithEncoding : StringWriter
|
||||
{
|
||||
private readonly Encoding _encoding;
|
||||
|
||||
public StringWriterWithEncoding()
|
||||
{
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(IFormatProvider formatProvider)
|
||||
: base(formatProvider)
|
||||
{
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(StringBuilder sb)
|
||||
: base(sb)
|
||||
{
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider)
|
||||
: base(sb, formatProvider)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public StringWriterWithEncoding(Encoding encoding)
|
||||
{
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(IFormatProvider formatProvider, Encoding encoding)
|
||||
: base(formatProvider)
|
||||
{
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(StringBuilder sb, Encoding encoding)
|
||||
: base(sb)
|
||||
{
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
public StringWriterWithEncoding(StringBuilder sb, IFormatProvider formatProvider, Encoding encoding)
|
||||
: base(sb, formatProvider)
|
||||
{
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
|
||||
}
|
||||
}
|
||||
589
Emby.Dlna/DlnaManager.cs
Normal file
589
Emby.Dlna/DlnaManager.cs
Normal file
@@ -0,0 +1,589 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Dlna.Profiles;
|
||||
using Emby.Dlna.Server;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Dlna;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Drawing;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class DlnaManager : IDlnaManager
|
||||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private static readonly Assembly _assembly = typeof(DlnaManager).Assembly;
|
||||
|
||||
private readonly Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>> _profiles = new Dictionary<string, Tuple<InternalProfileInfo, DeviceProfile>>(StringComparer.Ordinal);
|
||||
|
||||
public DlnaManager(
|
||||
IXmlSerializer xmlSerializer,
|
||||
IFileSystem fileSystem,
|
||||
IApplicationPaths appPaths,
|
||||
ILoggerFactory loggerFactory,
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerApplicationHost appHost)
|
||||
{
|
||||
_xmlSerializer = xmlSerializer;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
_logger = loggerFactory.CreateLogger("Dlna");
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
public async Task InitProfilesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExtractSystemProfilesAsync();
|
||||
LoadProfiles();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error extracting DLNA profiles.");
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadProfiles()
|
||||
{
|
||||
var list = GetProfiles(UserProfilesPath, DeviceProfileType.User)
|
||||
.OrderBy(i => i.Name)
|
||||
.ToList();
|
||||
|
||||
list.AddRange(GetProfiles(SystemProfilesPath, DeviceProfileType.System)
|
||||
.OrderBy(i => i.Name));
|
||||
}
|
||||
|
||||
public IEnumerable<DeviceProfile> GetProfiles()
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
var list = _profiles.Values.ToList();
|
||||
return list
|
||||
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||
.ThenBy(i => i.Item1.Info.Name)
|
||||
.Select(i => i.Item2)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public DeviceProfile GetDefaultProfile()
|
||||
{
|
||||
return new DefaultProfile();
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(DeviceIdentification deviceInfo)
|
||||
{
|
||||
if (deviceInfo == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(deviceInfo));
|
||||
}
|
||||
|
||||
var profile = GetProfiles()
|
||||
.FirstOrDefault(i => i.Identification != null && IsMatch(deviceInfo, i.Identification));
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogUnmatchedProfile(deviceInfo);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private void LogUnmatchedProfile(DeviceIdentification profile)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
|
||||
builder.AppendLine("No matching device profile found. The default will need to be used.");
|
||||
builder.AppendLine(string.Format("DeviceDescription:{0}", profile.DeviceDescription ?? string.Empty));
|
||||
builder.AppendLine(string.Format("FriendlyName:{0}", profile.FriendlyName ?? string.Empty));
|
||||
builder.AppendLine(string.Format("Manufacturer:{0}", profile.Manufacturer ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ManufacturerUrl:{0}", profile.ManufacturerUrl ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelDescription:{0}", profile.ModelDescription ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelName:{0}", profile.ModelName ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelNumber:{0}", profile.ModelNumber ?? string.Empty));
|
||||
builder.AppendLine(string.Format("ModelUrl:{0}", profile.ModelUrl ?? string.Empty));
|
||||
builder.AppendLine(string.Format("SerialNumber:{0}", profile.SerialNumber ?? string.Empty));
|
||||
|
||||
_logger.LogInformation(builder.ToString());
|
||||
}
|
||||
|
||||
private bool IsMatch(DeviceIdentification deviceInfo, DeviceIdentification profileInfo)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(profileInfo.DeviceDescription))
|
||||
{
|
||||
if (deviceInfo.DeviceDescription == null || !IsRegexMatch(deviceInfo.DeviceDescription, profileInfo.DeviceDescription))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.FriendlyName))
|
||||
{
|
||||
if (deviceInfo.FriendlyName == null || !IsRegexMatch(deviceInfo.FriendlyName, profileInfo.FriendlyName))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.Manufacturer))
|
||||
{
|
||||
if (deviceInfo.Manufacturer == null || !IsRegexMatch(deviceInfo.Manufacturer, profileInfo.Manufacturer))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ManufacturerUrl))
|
||||
{
|
||||
if (deviceInfo.ManufacturerUrl == null || !IsRegexMatch(deviceInfo.ManufacturerUrl, profileInfo.ManufacturerUrl))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelDescription))
|
||||
{
|
||||
if (deviceInfo.ModelDescription == null || !IsRegexMatch(deviceInfo.ModelDescription, profileInfo.ModelDescription))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelName))
|
||||
{
|
||||
if (deviceInfo.ModelName == null || !IsRegexMatch(deviceInfo.ModelName, profileInfo.ModelName))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelNumber))
|
||||
{
|
||||
if (deviceInfo.ModelNumber == null || !IsRegexMatch(deviceInfo.ModelNumber, profileInfo.ModelNumber))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.ModelUrl))
|
||||
{
|
||||
if (deviceInfo.ModelUrl == null || !IsRegexMatch(deviceInfo.ModelUrl, profileInfo.ModelUrl))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileInfo.SerialNumber))
|
||||
{
|
||||
if (deviceInfo.SerialNumber == null || !IsRegexMatch(deviceInfo.SerialNumber, profileInfo.SerialNumber))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsRegexMatch(string input, string pattern)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Regex.IsMatch(input, pattern);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error evaluating regex pattern {Pattern}", pattern);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(IHeaderDictionary headers)
|
||||
{
|
||||
if (headers == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(headers));
|
||||
}
|
||||
|
||||
var profile = GetProfiles().FirstOrDefault(i => i.Identification != null && IsMatch(headers, i.Identification));
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
_logger.LogDebug("Found matching device profile: {0}", profile.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
var headerString = string.Join(", ", headers.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
||||
_logger.LogDebug("No matching device profile found. {0}", headerString);
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
private bool IsMatch(IHeaderDictionary headers, DeviceIdentification profileInfo)
|
||||
{
|
||||
return profileInfo.Headers.Any(i => IsMatch(headers, i));
|
||||
}
|
||||
|
||||
private bool IsMatch(IHeaderDictionary headers, HttpHeaderInfo header)
|
||||
{
|
||||
// Handle invalid user setup
|
||||
if (string.IsNullOrEmpty(header.Name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (headers.TryGetValue(header.Name, out StringValues value))
|
||||
{
|
||||
switch (header.Match)
|
||||
{
|
||||
case HeaderMatchType.Equals:
|
||||
return string.Equals(value, header.Value, StringComparison.OrdinalIgnoreCase);
|
||||
case HeaderMatchType.Substring:
|
||||
var isMatch = value.ToString().IndexOf(header.Value, StringComparison.OrdinalIgnoreCase) != -1;
|
||||
//_logger.LogDebug("IsMatch-Substring value: {0} testValue: {1} isMatch: {2}", value, header.Value, isMatch);
|
||||
return isMatch;
|
||||
case HeaderMatchType.Regex:
|
||||
return Regex.IsMatch(value, header.Value, RegexOptions.IgnoreCase);
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized HeaderMatchType");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private string UserProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "user");
|
||||
|
||||
private string SystemProfilesPath => Path.Combine(_appPaths.ConfigurationDirectoryPath, "dlna", "system");
|
||||
|
||||
private IEnumerable<DeviceProfile> GetProfiles(string path, DeviceProfileType type)
|
||||
{
|
||||
try
|
||||
{
|
||||
var xmlFies = _fileSystem.GetFilePaths(path)
|
||||
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
return xmlFies
|
||||
.Select(i => ParseProfileFile(i, type))
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<DeviceProfile>();
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceProfile ParseProfileFile(string path, DeviceProfileType type)
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
if (_profiles.TryGetValue(path, out Tuple<InternalProfileInfo, DeviceProfile> profileTuple))
|
||||
{
|
||||
return profileTuple.Item2;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
DeviceProfile profile;
|
||||
|
||||
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
|
||||
|
||||
profile = ReserializeProfile(tempProfile);
|
||||
|
||||
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||
|
||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||
|
||||
return profile;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error parsing profile file: {Path}", path);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DeviceProfile GetProfile(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
||||
var info = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return ParseProfileFile(info.Path, info.Info.Type);
|
||||
}
|
||||
|
||||
private IEnumerable<InternalProfileInfo> GetProfileInfosInternal()
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
var list = _profiles.Values.ToList();
|
||||
return list
|
||||
.Select(i => i.Item1)
|
||||
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
|
||||
.ThenBy(i => i.Info.Name);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<DeviceProfileInfo> GetProfileInfos()
|
||||
{
|
||||
return GetProfileInfosInternal().Select(i => i.Info);
|
||||
}
|
||||
|
||||
private InternalProfileInfo GetInternalProfileInfo(FileSystemMetadata file, DeviceProfileType type)
|
||||
{
|
||||
return new InternalProfileInfo
|
||||
{
|
||||
Path = file.FullName,
|
||||
|
||||
Info = new DeviceProfileInfo
|
||||
{
|
||||
Id = file.FullName.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
Name = _fileSystem.GetFileNameWithoutExtension(file),
|
||||
Type = type
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ExtractSystemProfilesAsync()
|
||||
{
|
||||
var namespaceName = GetType().Namespace + ".Profiles.Xml.";
|
||||
|
||||
var systemProfilesPath = SystemProfilesPath;
|
||||
|
||||
foreach (var name in _assembly.GetManifestResourceNames())
|
||||
{
|
||||
if (!name.StartsWith(namespaceName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var filename = Path.GetFileName(name).Substring(namespaceName.Length);
|
||||
|
||||
var path = Path.Combine(systemProfilesPath, filename);
|
||||
|
||||
using (var stream = _assembly.GetManifestResourceStream(name))
|
||||
{
|
||||
var fileInfo = _fileSystem.GetFileInfo(path);
|
||||
|
||||
if (!fileInfo.Exists || fileInfo.Length != stream.Length)
|
||||
{
|
||||
Directory.CreateDirectory(systemProfilesPath);
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not necessary, but just to make it easy to find
|
||||
Directory.CreateDirectory(UserProfilesPath);
|
||||
}
|
||||
|
||||
public void DeleteProfile(string id)
|
||||
{
|
||||
var info = GetProfileInfosInternal().First(i => string.Equals(id, i.Info.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (info.Info.Type == DeviceProfileType.System)
|
||||
{
|
||||
throw new ArgumentException("System profiles cannot be deleted.");
|
||||
}
|
||||
|
||||
_fileSystem.DeleteFile(info.Path);
|
||||
|
||||
lock (_profiles)
|
||||
{
|
||||
_profiles.Remove(info.Path);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
||||
if (string.IsNullOrEmpty(profile.Name))
|
||||
{
|
||||
throw new ArgumentException("Profile is missing Name");
|
||||
}
|
||||
|
||||
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
||||
var path = Path.Combine(UserProfilesPath, newFilename);
|
||||
|
||||
SaveProfile(profile, path, DeviceProfileType.User);
|
||||
}
|
||||
|
||||
public void UpdateProfile(DeviceProfile profile)
|
||||
{
|
||||
profile = ReserializeProfile(profile);
|
||||
|
||||
if (string.IsNullOrEmpty(profile.Id))
|
||||
{
|
||||
throw new ArgumentException("Profile is missing Id");
|
||||
}
|
||||
if (string.IsNullOrEmpty(profile.Name))
|
||||
{
|
||||
throw new ArgumentException("Profile is missing Name");
|
||||
}
|
||||
|
||||
var current = GetProfileInfosInternal().First(i => string.Equals(i.Info.Id, profile.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var newFilename = _fileSystem.GetValidFilename(profile.Name) + ".xml";
|
||||
var path = Path.Combine(UserProfilesPath, newFilename);
|
||||
|
||||
if (!string.Equals(path, current.Path, StringComparison.Ordinal) &&
|
||||
current.Info.Type != DeviceProfileType.System)
|
||||
{
|
||||
_fileSystem.DeleteFile(current.Path);
|
||||
}
|
||||
|
||||
SaveProfile(profile, path, DeviceProfileType.User);
|
||||
}
|
||||
|
||||
private void SaveProfile(DeviceProfile profile, string path, DeviceProfileType type)
|
||||
{
|
||||
lock (_profiles)
|
||||
{
|
||||
_profiles[path] = new Tuple<InternalProfileInfo, DeviceProfile>(GetInternalProfileInfo(_fileSystem.GetFileInfo(path), type), profile);
|
||||
}
|
||||
SerializeToXml(profile, path);
|
||||
}
|
||||
|
||||
internal void SerializeToXml(DeviceProfile profile, string path)
|
||||
{
|
||||
_xmlSerializer.SerializeToFile(profile, path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates the object using serialization, to ensure it's not a subclass.
|
||||
/// If it's a subclass it may not serlialize properly to xml (different root element tag name)
|
||||
/// </summary>
|
||||
/// <param name="profile"></param>
|
||||
/// <returns></returns>
|
||||
private DeviceProfile ReserializeProfile(DeviceProfile profile)
|
||||
{
|
||||
if (profile.GetType() == typeof(DeviceProfile))
|
||||
{
|
||||
return profile;
|
||||
}
|
||||
|
||||
var json = _jsonSerializer.SerializeToString(profile);
|
||||
|
||||
return _jsonSerializer.DeserializeFromString<DeviceProfile>(json);
|
||||
}
|
||||
|
||||
class InternalProfileInfo
|
||||
{
|
||||
internal DeviceProfileInfo Info { get; set; }
|
||||
internal string Path { get; set; }
|
||||
}
|
||||
|
||||
public string GetServerDescriptionXml(IHeaderDictionary headers, string serverUuId, string serverAddress)
|
||||
{
|
||||
var profile = GetProfile(headers) ??
|
||||
GetDefaultProfile();
|
||||
|
||||
var serverId = _appHost.SystemId;
|
||||
|
||||
return new DescriptionXmlBuilder(profile, serverUuId, serverAddress, _appHost.FriendlyName, serverId).GetXml();
|
||||
}
|
||||
|
||||
public ImageStream GetIcon(string filename)
|
||||
{
|
||||
var format = filename.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
|
||||
? ImageFormat.Png
|
||||
: ImageFormat.Jpg;
|
||||
|
||||
var resource = GetType().Namespace + ".Images." + filename.ToLowerInvariant();
|
||||
|
||||
return new ImageStream
|
||||
{
|
||||
Format = format,
|
||||
Stream = _assembly.GetManifestResourceStream(resource)
|
||||
};
|
||||
}
|
||||
}
|
||||
/*
|
||||
class DlnaProfileEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IXmlSerializer _xmlSerializer;
|
||||
|
||||
public DlnaProfileEntryPoint(IApplicationPaths appPaths, IFileSystem fileSystem, IXmlSerializer xmlSerializer)
|
||||
{
|
||||
_appPaths = appPaths;
|
||||
_fileSystem = fileSystem;
|
||||
_xmlSerializer = xmlSerializer;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
DumpProfiles();
|
||||
}
|
||||
|
||||
private void DumpProfiles()
|
||||
{
|
||||
DeviceProfile[] list = new []
|
||||
{
|
||||
new SamsungSmartTvProfile(),
|
||||
new XboxOneProfile(),
|
||||
new SonyPs3Profile(),
|
||||
new SonyPs4Profile(),
|
||||
new SonyBravia2010Profile(),
|
||||
new SonyBravia2011Profile(),
|
||||
new SonyBravia2012Profile(),
|
||||
new SonyBravia2013Profile(),
|
||||
new SonyBravia2014Profile(),
|
||||
new SonyBlurayPlayer2013(),
|
||||
new SonyBlurayPlayer2014(),
|
||||
new SonyBlurayPlayer2015(),
|
||||
new SonyBlurayPlayer2016(),
|
||||
new SonyBlurayPlayerProfile(),
|
||||
new PanasonicVieraProfile(),
|
||||
new WdtvLiveProfile(),
|
||||
new DenonAvrProfile(),
|
||||
new LinksysDMA2100Profile(),
|
||||
new LgTvProfile(),
|
||||
new Foobar2000Profile(),
|
||||
new SharpSmartTvProfile(),
|
||||
new MediaMonkeyProfile(),
|
||||
//new Windows81Profile(),
|
||||
//new WindowsMediaCenterProfile(),
|
||||
//new WindowsPhoneProfile(),
|
||||
new DirectTvProfile(),
|
||||
new DishHopperJoeyProfile(),
|
||||
new DefaultProfile(),
|
||||
new PopcornHourProfile(),
|
||||
new MarantzProfile()
|
||||
};
|
||||
|
||||
foreach (var item in list)
|
||||
{
|
||||
var path = Path.Combine(_appPaths.ProgramDataPath, _fileSystem.GetValidFilename(item.Name) + ".xml");
|
||||
|
||||
_xmlSerializer.SerializeToFile(item, path);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}*/
|
||||
}
|
||||
67
Emby.Dlna/Emby.Dlna.csproj
Normal file
67
Emby.Dlna/Emby.Dlna.csproj
Normal file
@@ -0,0 +1,67 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj" />
|
||||
<ProjectReference Include="..\RSSDP\RSSDP.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Images\logo120.jpg" />
|
||||
<EmbeddedResource Include="Images\logo120.png" />
|
||||
<EmbeddedResource Include="Images\logo240.jpg" />
|
||||
<EmbeddedResource Include="Images\logo240.png" />
|
||||
<EmbeddedResource Include="Images\logo48.jpg" />
|
||||
<EmbeddedResource Include="Images\logo48.png" />
|
||||
<EmbeddedResource Include="Images\people48.jpg" />
|
||||
<EmbeddedResource Include="Images\people48.png" />
|
||||
<EmbeddedResource Include="Images\people480.jpg" />
|
||||
<EmbeddedResource Include="Images\people480.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Profiles\Xml\Default.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Denon AVR.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\DirecTV HD-DVR.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Dish Hopper-Joey.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\foobar2000.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\LG Smart TV.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Linksys DMA2100.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Marantz.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\MediaMonkey.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Panasonic Viera.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Popcorn Hour.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Samsung Smart TV.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2013.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2014.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2015.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player 2016.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Blu-ray Player.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282010%29.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282011%29.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282012%29.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282013%29.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony Bravia %282014%29.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 3.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Sony PlayStation 4.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\WDTV Live.xml" />
|
||||
<EmbeddedResource Include="Profiles\Xml\Xbox One.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
17
Emby.Dlna/EventSubscriptionResponse.cs
Normal file
17
Emby.Dlna/EventSubscriptionResponse.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Dlna
|
||||
{
|
||||
public class EventSubscriptionResponse
|
||||
{
|
||||
public string Content { get; set; }
|
||||
public string ContentType { get; set; }
|
||||
|
||||
public Dictionary<string, string> Headers { get; set; }
|
||||
|
||||
public EventSubscriptionResponse()
|
||||
{
|
||||
Headers = new Dictionary<string, string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user