Merge branch 'master' into externalid-type
This commit is contained in:
commit
96acd6481e
|
@ -1,13 +1,13 @@
|
||||||
parameters:
|
parameters:
|
||||||
- name: Packages
|
- name: Packages
|
||||||
type: object
|
type: object
|
||||||
default: {}
|
default: {}
|
||||||
- name: LinuxImage
|
- name: LinuxImage
|
||||||
type: string
|
type: string
|
||||||
default: "ubuntu-latest"
|
default: "ubuntu-latest"
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
type: string
|
type: string
|
||||||
default: 3.1.100
|
default: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: CompatibilityCheck
|
- job: CompatibilityCheck
|
||||||
|
@ -23,7 +23,7 @@ jobs:
|
||||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||||
maxParallel: 2
|
maxParallel: 2
|
||||||
dependsOn: MainBuild
|
dependsOn: Build
|
||||||
steps:
|
steps:
|
||||||
- checkout: none
|
- checkout: none
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,14 @@ parameters:
|
||||||
DotNetSdkVersion: 3.1.100
|
DotNetSdkVersion: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: MainBuild
|
- job: Build
|
||||||
displayName: Main Build
|
displayName: Build
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
Release:
|
Release:
|
||||||
BuildConfiguration: Release
|
BuildConfiguration: Release
|
||||||
Debug:
|
Debug:
|
||||||
BuildConfiguration: Debug
|
BuildConfiguration: Debug
|
||||||
maxParallel: 2
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "${{ parameters.LinuxImage }}"
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
steps:
|
steps:
|
||||||
|
@ -21,41 +20,34 @@ jobs:
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
displayName: "Download Web Branch"
|
||||||
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'))
|
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
||||||
inputs:
|
inputs:
|
||||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
path: '$(Agent.TempDirectory)'
|
||||||
|
artifact: 'jellyfin-web-production'
|
||||||
|
source: 'specific'
|
||||||
|
project: 'jellyfin'
|
||||||
|
pipeline: 'Jellyfin Web'
|
||||||
|
runBranch: variables['Build.SourceBranch']
|
||||||
|
|
||||||
- task: CmdLine@2
|
- task: DownloadPipelineArtifact@2
|
||||||
displayName: "Clone Web Client (PR)"
|
displayName: "Download Web Target"
|
||||||
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'))
|
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||||
inputs:
|
inputs:
|
||||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
path: '$(Agent.TempDirectory)'
|
||||||
|
artifact: 'jellyfin-web-production'
|
||||||
|
source: 'specific'
|
||||||
|
project: 'jellyfin'
|
||||||
|
pipeline: 'Jellyfin Web'
|
||||||
|
runBranch: variables['System.PullRequest.TargetBranch']
|
||||||
|
|
||||||
- task: NodeTool@0
|
- task: ExtractFiles@1
|
||||||
displayName: "Install Node"
|
displayName: "Extract Web Client"
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
inputs:
|
||||||
versionSpec: "10.x"
|
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
||||||
|
destinationFolder: '$(Build.SourcesDirectory)/MediaBrowser.WebDashboard'
|
||||||
- task: CmdLine@2
|
cleanDestinationFolder: false
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: false
|
|
||||||
|
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: "Update DotNet"
|
displayName: "Update DotNet"
|
||||||
|
@ -69,33 +61,33 @@ jobs:
|
||||||
command: publish
|
command: publish
|
||||||
publishWebProjects: false
|
publishWebProjects: false
|
||||||
projects: "${{ parameters.RestoreBuildProjects }}"
|
projects: "${{ parameters.RestoreBuildProjects }}"
|
||||||
arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
|
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
|
||||||
zipAfterPublish: false
|
zipAfterPublish: false
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Naming"
|
displayName: "Publish Artifact Naming"
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
|
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||||
artifactName: "Jellyfin.Naming"
|
artifactName: "Jellyfin.Naming"
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Controller"
|
displayName: "Publish Artifact Controller"
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||||
artifactName: "Jellyfin.Controller"
|
artifactName: "Jellyfin.Controller"
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Model"
|
displayName: "Publish Artifact Model"
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||||
artifactName: "Jellyfin.Model"
|
artifactName: "Jellyfin.Model"
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
- task: PublishPipelineArtifact@0
|
||||||
displayName: "Publish Artifact Common"
|
displayName: "Publish Artifact Common"
|
||||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
inputs:
|
inputs:
|
||||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||||
artifactName: "Jellyfin.Common"
|
artifactName: "Jellyfin.Common"
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
parameters:
|
parameters:
|
||||||
- name: ImageNames
|
- name: ImageNames
|
||||||
type: object
|
type: object
|
||||||
default:
|
default:
|
||||||
Linux: "ubuntu-latest"
|
Linux: "ubuntu-latest"
|
||||||
Windows: "windows-latest"
|
Windows: "windows-latest"
|
||||||
macOS: "macos-latest"
|
macOS: "macos-latest"
|
||||||
- name: TestProjects
|
- name: TestProjects
|
||||||
type: string
|
type: string
|
||||||
default: "tests/**/*Tests.csproj"
|
default: "tests/**/*Tests.csproj"
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
type: string
|
type: string
|
||||||
default: 3.1.100
|
default: 3.1.100
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: MainTest
|
- job: Test
|
||||||
displayName: Main Test
|
displayName: Test
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
${{ each imageName in parameters.ImageNames }}:
|
${{ each imageName in parameters.ImageNames }}:
|
||||||
${{ imageName.key }}:
|
${{ imageName.key }}:
|
||||||
ImageName: ${{ imageName.value }}
|
ImageName: ${{ imageName.value }}
|
||||||
maxParallel: 3
|
|
||||||
pool:
|
pool:
|
||||||
vmImage: "$(ImageName)"
|
vmImage: "$(ImageName)"
|
||||||
steps:
|
steps:
|
||||||
|
@ -29,14 +28,30 @@ jobs:
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: false
|
persistCredentials: false
|
||||||
|
|
||||||
|
# This is required for the SonarCloud analyzer
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: "Install .NET Core SDK 2.1"
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
inputs:
|
||||||
|
packageType: sdk
|
||||||
|
version: '2.1.805'
|
||||||
|
|
||||||
- task: UseDotNet@2
|
- task: UseDotNet@2
|
||||||
displayName: "Update DotNet"
|
displayName: "Update DotNet"
|
||||||
inputs:
|
inputs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: SonarCloudPrepare@1
|
||||||
|
displayName: 'Prepare analysis on SonarCloud'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
inputs:
|
||||||
|
SonarCloud: 'Sonarcloud for Jellyfin'
|
||||||
|
organization: 'jellyfin'
|
||||||
|
projectKey: 'jellyfin_jellyfin'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2
|
||||||
displayName: Run .NET Core CLI tests
|
displayName: 'Run CLI Tests'
|
||||||
inputs:
|
inputs:
|
||||||
command: "test"
|
command: "test"
|
||||||
projects: ${{ parameters.TestProjects }}
|
projects: ${{ parameters.TestProjects }}
|
||||||
|
@ -45,9 +60,17 @@ jobs:
|
||||||
testRunTitle: $(Agent.JobName)
|
testRunTitle: $(Agent.JobName)
|
||||||
workingDirectory: "$(Build.SourcesDirectory)"
|
workingDirectory: "$(Build.SourcesDirectory)"
|
||||||
|
|
||||||
|
- task: SonarCloudAnalyze@1
|
||||||
|
displayName: 'Run Code Analysis'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
|
||||||
|
- task: SonarCloudPublish@1
|
||||||
|
displayName: 'Publish Quality Gate Result'
|
||||||
|
condition: eq(variables['ImageName'], 'ubuntu-latest')
|
||||||
|
|
||||||
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
displayName: ReportGenerator (merge)
|
displayName: 'Run ReportGenerator'
|
||||||
inputs:
|
inputs:
|
||||||
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
targetdir: "$(Agent.TempDirectory)/merged/"
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
|
@ -56,10 +79,11 @@ jobs:
|
||||||
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
||||||
- task: PublishCodeCoverageResults@1
|
- task: PublishCodeCoverageResults@1
|
||||||
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
displayName: Publish Code Coverage
|
displayName: 'Publish Code Coverage'
|
||||||
inputs:
|
inputs:
|
||||||
codeCoverageTool: "cobertura"
|
codeCoverageTool: "cobertura"
|
||||||
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||||
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
pathToSources: $(Build.SourcesDirectory)
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
failIfCoverageEmpty: true
|
failIfCoverageEmpty: true
|
||||||
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: "tests/**/*Tests.csproj"
|
|
||||||
DotNetSdkVersion: 3.1.100
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: PublishWindows
|
|
||||||
displayName: Publish Windows
|
|
||||||
pool:
|
|
||||||
vmImage: ${{ parameters.WindowsImage }}
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (PR)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
|
|
||||||
inputs:
|
|
||||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
|
||||||
|
|
||||||
- task: NodeTool@0
|
|
||||||
displayName: "Install Node"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
versionSpec: "10.x"
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
|
||||||
contents: "**"
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: false
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone UX Repository"
|
|
||||||
inputs:
|
|
||||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
|
||||||
|
|
||||||
- task: PowerShell@2
|
|
||||||
displayName: "Build NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
targetType: "filePath"
|
|
||||||
filePath: ./deployment/windows/build-jellyfin.ps1
|
|
||||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
|
||||||
errorActionPreference: "stop"
|
|
||||||
workingDirectory: $(Build.SourcesDirectory)
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: "Copy NSIS Installer"
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
|
|
||||||
contents: "jellyfin*.exe"
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
|
||||||
cleanTargetFolder: true
|
|
||||||
overWrite: true
|
|
||||||
flattenFolders: true
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: "Publish Artifact Setup"
|
|
||||||
condition: succeeded()
|
|
||||||
inputs:
|
|
||||||
targetPath: "$(build.artifactstagingdirectory)/setup"
|
|
||||||
artifactName: "Jellyfin Server Setup"
|
|
|
@ -1,12 +1,12 @@
|
||||||
name: $(Date:yyyyMMdd)$(Rev:.r)
|
name: $(Date:yyyyMMdd)$(Rev:.r)
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
- name: TestProjects
|
- name: TestProjects
|
||||||
value: "tests/**/*Tests.csproj"
|
value: "tests/**/*Tests.csproj"
|
||||||
- name: RestoreBuildProjects
|
- name: RestoreBuildProjects
|
||||||
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
value: "Jellyfin.Server/Jellyfin.Server.csproj"
|
||||||
- name: DotNetSdkVersion
|
- name: DotNetSdkVersion
|
||||||
value: 3.1.100
|
value: 3.1.100
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
autoCancel: true
|
autoCancel: true
|
||||||
|
@ -27,11 +27,6 @@ jobs:
|
||||||
Windows: "windows-latest"
|
Windows: "windows-latest"
|
||||||
macOS: "macos-latest"
|
macOS: "macos-latest"
|
||||||
|
|
||||||
- template: azure-pipelines-windows.yml
|
|
||||||
parameters:
|
|
||||||
WindowsImage: "windows-latest"
|
|
||||||
TestProjects: $(TestProjects)
|
|
||||||
|
|
||||||
- template: azure-pipelines-compat.yml
|
- template: azure-pipelines-compat.yml
|
||||||
parameters:
|
parameters:
|
||||||
Packages:
|
Packages:
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
|
|
||||||
deployment/fedora-package-x64/pkg-src/jellyfin.spec)
|
|
||||||
|
|
||||||
deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
|
|
||||||
curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
|
||||||
https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
|
|
||||||
|| curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
|
|
||||||
https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
|
|
||||||
|
|
||||||
srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
|
|
||||||
cd deployment/fedora-package-x64; \
|
|
||||||
SOURCE_DIR=../.. \
|
|
||||||
WORKDIR="$${PWD}"; \
|
|
||||||
package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
|
|
||||||
pkg_src_dir="$${WORKDIR}/pkg-src"; \
|
|
||||||
GNU_TAR=1; \
|
|
||||||
tar \
|
|
||||||
--transform "s,^\.,jellyfin-$(VERSION)," \
|
|
||||||
--exclude='.git*' \
|
|
||||||
--exclude='**/.git' \
|
|
||||||
--exclude='**/.hg' \
|
|
||||||
--exclude='**/.vs' \
|
|
||||||
--exclude='**/.vscode' \
|
|
||||||
--exclude='deployment' \
|
|
||||||
--exclude='**/bin' \
|
|
||||||
--exclude='**/obj' \
|
|
||||||
--exclude='**/.nuget' \
|
|
||||||
--exclude='*.deb' \
|
|
||||||
--exclude='*.rpm' \
|
|
||||||
-czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C $${SOURCE_DIR} ./ || GNU_TAR=0; \
|
|
||||||
if [ $${GNU_TAR} -eq 0 ]; then \
|
|
||||||
package_temporary_dir="$$(mktemp -d)"; \
|
|
||||||
mkdir -p "$${package_temporary_dir}/jellyfin"; \
|
|
||||||
tar \
|
|
||||||
--exclude='.git*' \
|
|
||||||
--exclude='**/.git' \
|
|
||||||
--exclude='**/.hg' \
|
|
||||||
--exclude='**/.vs' \
|
|
||||||
--exclude='**/.vscode' \
|
|
||||||
--exclude='deployment' \
|
|
||||||
--exclude='**/bin' \
|
|
||||||
--exclude='**/obj' \
|
|
||||||
--exclude='**/.nuget' \
|
|
||||||
--exclude='*.deb' \
|
|
||||||
--exclude='*.rpm' \
|
|
||||||
-czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C $${SOURCE_DIR} ./; \
|
|
||||||
mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
|
|
||||||
tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C "$${package_temporary_dir}/jellyfin-$(VERSION); \
|
|
||||||
rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
|
|
||||||
tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
|
|
||||||
-C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
|
|
||||||
rm -rf $${package_temporary_dir}; \
|
|
||||||
fi; \
|
|
||||||
rpmbuild -bs pkg-src/jellyfin.spec \
|
|
||||||
--define "_sourcedir $$PWD/pkg-src/" \
|
|
||||||
--define "_srcrpmdir $(outdir)"
|
|
1
.copr/Makefile
Symbolic link
1
.copr/Makefile
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../fedora/Makefile
|
|
@ -13,7 +13,7 @@ charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
max_line_length = null
|
max_line_length = off
|
||||||
|
|
||||||
# YAML indentation
|
# YAML indentation
|
||||||
[*.{yml,yaml}]
|
[*.{yml,yaml}]
|
||||||
|
@ -22,6 +22,7 @@ indent_size = 2
|
||||||
# XML indentation
|
# XML indentation
|
||||||
[*.{csproj,xml}]
|
[*.{csproj,xml}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# .NET Coding Conventions #
|
# .NET Coding Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
@ -51,11 +52,12 @@ dotnet_style_explicit_tuple_names = true:suggestion
|
||||||
dotnet_style_null_propagation = true:suggestion
|
dotnet_style_null_propagation = true:suggestion
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
dotnet_style_coalesce_expression = true:suggestion
|
||||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
|
||||||
dotnet_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
dotnet_style_prefer_auto_properties = true:silent
|
dotnet_style_prefer_auto_properties = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# Naming Conventions #
|
# Naming Conventions #
|
||||||
###############################
|
###############################
|
||||||
|
@ -67,7 +69,7 @@ dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non
|
||||||
dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style
|
dotnet_naming_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_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_symbols.non_private_static_fields.required_modifiers = static
|
||||||
|
|
||||||
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case
|
||||||
|
@ -159,6 +161,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion
|
||||||
csharp_prefer_simple_default_expression = true:suggestion
|
csharp_prefer_simple_default_expression = true:suggestion
|
||||||
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
csharp_style_pattern_local_over_anonymous_function = true:suggestion
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
csharp_style_inlined_variable_declaration = true:suggestion
|
||||||
|
|
||||||
###############################
|
###############################
|
||||||
# C# Formatting Rules #
|
# C# Formatting Rules #
|
||||||
###############################
|
###############################
|
||||||
|
@ -189,9 +192,3 @@ csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
# Wrapping preferences
|
# Wrapping preferences
|
||||||
csharp_preserve_single_line_statements = true
|
csharp_preserve_single_line_statements = true
|
||||||
csharp_preserve_single_line_blocks = true
|
csharp_preserve_single_line_blocks = true
|
||||||
###############################
|
|
||||||
# VB Coding Conventions #
|
|
||||||
###############################
|
|
||||||
[*.vb]
|
|
||||||
# Modifier preferences
|
|
||||||
visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
|
|
||||||
|
|
3
.github/CODEOWNERS
vendored
Normal file
3
.github/CODEOWNERS
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# Joshua must review all changes to deployment and build.sh
|
||||||
|
deployment/* @joshuaboniface
|
||||||
|
build.sh @joshuaboniface
|
20
.gitignore
vendored
20
.gitignore
vendored
|
@ -39,6 +39,7 @@ ProgramData*/
|
||||||
CorePlugins*/
|
CorePlugins*/
|
||||||
ProgramData-Server*/
|
ProgramData-Server*/
|
||||||
ProgramData-UI*/
|
ProgramData-UI*/
|
||||||
|
MediaBrowser.WebDashboard/jellyfin-web/**
|
||||||
|
|
||||||
#################
|
#################
|
||||||
## Visual Studio
|
## Visual Studio
|
||||||
|
@ -244,14 +245,14 @@ pip-log.txt
|
||||||
#########################
|
#########################
|
||||||
|
|
||||||
# Artifacts for debian-x64
|
# Artifacts for debian-x64
|
||||||
deployment/debian-package-x64/pkg-src/.debhelper/
|
debian/.debhelper/
|
||||||
deployment/debian-package-x64/pkg-src/*.debhelper
|
debian/*.debhelper
|
||||||
deployment/debian-package-x64/pkg-src/debhelper-build-stamp
|
debian/debhelper-build-stamp
|
||||||
deployment/debian-package-x64/pkg-src/files
|
debian/files
|
||||||
deployment/debian-package-x64/pkg-src/jellyfin.substvars
|
debian/jellyfin.substvars
|
||||||
deployment/debian-package-x64/pkg-src/jellyfin/
|
debian/jellyfin/
|
||||||
# Don't ignore the debian/bin folder
|
# Don't ignore the debian/bin folder
|
||||||
!deployment/debian-package-x64/pkg-src/bin/
|
!debian/bin/
|
||||||
|
|
||||||
deployment/**/dist/
|
deployment/**/dist/
|
||||||
deployment/**/pkg-dist/
|
deployment/**/pkg-dist/
|
||||||
|
@ -271,3 +272,8 @@ dist
|
||||||
|
|
||||||
# BenchmarkDotNet artifacts
|
# BenchmarkDotNet artifacts
|
||||||
BenchmarkDotNet.Artifacts
|
BenchmarkDotNet.Artifacts
|
||||||
|
|
||||||
|
# Ignore web artifacts from native builds
|
||||||
|
web/
|
||||||
|
web-src.*
|
||||||
|
MediaBrowser.WebDashboard/jellyfin-web/
|
||||||
|
|
14
.vscode/extensions.json
vendored
Normal file
14
.vscode/extensions.json
vendored
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
|
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
|
||||||
|
|
||||||
|
// List of extensions which should be recommended for users of this workspace.
|
||||||
|
"recommendations": [
|
||||||
|
"ms-dotnettools.csharp",
|
||||||
|
"editorconfig.editorconfig"
|
||||||
|
],
|
||||||
|
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
- [cvium](https://github.com/cvium)
|
- [cvium](https://github.com/cvium)
|
||||||
- [dannymichel](https://github.com/dannymichel)
|
- [dannymichel](https://github.com/dannymichel)
|
||||||
- [DaveChild](https://github.com/DaveChild)
|
- [DaveChild](https://github.com/DaveChild)
|
||||||
|
- [Delgan](https://github.com/Delgan)
|
||||||
- [dcrdev](https://github.com/dcrdev)
|
- [dcrdev](https://github.com/dcrdev)
|
||||||
- [dhartung](https://github.com/dhartung)
|
- [dhartung](https://github.com/dhartung)
|
||||||
- [dinki](https://github.com/dinki)
|
- [dinki](https://github.com/dinki)
|
||||||
|
@ -128,6 +129,7 @@
|
||||||
- [xosdy](https://github.com/xosdy)
|
- [xosdy](https://github.com/xosdy)
|
||||||
- [XVicarious](https://github.com/XVicarious)
|
- [XVicarious](https://github.com/XVicarious)
|
||||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||||
|
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
33
Dockerfile
33
Dockerfile
|
@ -1,5 +1,4 @@
|
||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.1
|
||||||
ARG FFMPEG_VERSION=latest
|
|
||||||
|
|
||||||
FROM node:alpine as web-builder
|
FROM node:alpine as web-builder
|
||||||
ARG JELLYFIN_WEB_VERSION=master
|
ARG JELLYFIN_WEB_VERSION=master
|
||||||
|
@ -17,7 +16,6 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
|
|
||||||
FROM debian:buster-slim
|
FROM debian:buster-slim
|
||||||
|
|
||||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||||
|
@ -27,37 +25,36 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||||
|
|
||||||
COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
# Install dependencies:
|
# Install dependencies:
|
||||||
# libfontconfig1: needed for Skia
|
# mesa-va-drivers: needed for AMD VAAPI
|
||||||
# libgomp1: needed for ffmpeg
|
|
||||||
# libva-drm2: needed for ffmpeg
|
|
||||||
# mesa-va-drivers: needed for VAAPI
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
|
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
|
||||||
|
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
|
||||||
|
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||||
|
&& apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
libfontconfig1 \
|
|
||||||
libgomp1 \
|
|
||||||
libva-drm2 \
|
|
||||||
mesa-va-drivers \
|
mesa-va-drivers \
|
||||||
|
jellyfin-ffmpeg \
|
||||||
openssl \
|
openssl \
|
||||||
ca-certificates \
|
locales \
|
||||||
vainfo \
|
&& apt-get remove gnupg wget apt-transport-https -y \
|
||||||
i965-va-driver \
|
&& apt-get clean autoclean -y \
|
||||||
&& apt-get clean autoclean -y\
|
&& apt-get autoremove -y \
|
||||||
&& apt-get autoremove -y\
|
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media \
|
&& chmod 777 /cache /config /media \
|
||||||
&& ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
|
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
&& ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin
|
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/local/bin/ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
|
|
@ -52,20 +52,26 @@ RUN apt-get update \
|
||||||
libraspberrypi0 \
|
libraspberrypi0 \
|
||||||
vainfo \
|
vainfo \
|
||||||
libva2 \
|
libva2 \
|
||||||
|
locales \
|
||||||
&& apt-get remove curl gnupg -y \
|
&& apt-get remove curl gnupg -y \
|
||||||
&& apt-get clean autoclean -y \
|
&& apt-get clean autoclean -y \
|
||||||
&& apt-get autoremove -y \
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media \
|
||||||
|
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||||
"--datadir", "/config", \
|
"--datadir", "/config", \
|
||||||
"--cachedir", "/cache", \
|
"--cachedir", "/cache", \
|
||||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||||
|
|
|
@ -42,15 +42,21 @@ RUN apt-get update && apt-get install --no-install-recommends --no-install-sugge
|
||||||
libfreetype6 \
|
libfreetype6 \
|
||||||
libomxil-bellagio0 \
|
libomxil-bellagio0 \
|
||||||
libomxil-bellagio-bin \
|
libomxil-bellagio-bin \
|
||||||
|
locales \
|
||||||
&& apt-get clean autoclean -y \
|
&& apt-get clean autoclean -y \
|
||||||
&& apt-get autoremove -y \
|
&& apt-get autoremove -y \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& mkdir -p /cache /config /media \
|
&& mkdir -p /cache /config /media \
|
||||||
&& chmod 777 /cache /config /media
|
&& chmod 777 /cache /config /media \
|
||||||
|
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||||
|
|
||||||
COPY --from=builder /jellyfin /jellyfin
|
COPY --from=builder /jellyfin /jellyfin
|
||||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
|
|
||||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
|
||||||
EXPOSE 8096
|
EXPOSE 8096
|
||||||
VOLUME /cache /config /media
|
VOLUME /cache /config /media
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Buffers.Binary;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib
|
namespace DvdLib
|
||||||
|
@ -12,19 +14,12 @@ namespace DvdLib
|
||||||
|
|
||||||
public override ushort ReadUInt16()
|
public override ushort ReadUInt16()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
|
return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override uint ReadUInt32()
|
public override uint ReadUInt32()
|
||||||
{
|
{
|
||||||
return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
|
return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] ReadAndReverseBytes(int count)
|
|
||||||
{
|
|
||||||
byte[] val = base.ReadBytes(count);
|
|
||||||
Array.Reverse(val, 0, count);
|
|
||||||
return val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{713F42B5-878E-499D-A878-E4C652B1D5E8}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
public class Chapter
|
public class Chapter
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
|
@ -13,13 +14,10 @@ namespace DvdLib.Ifo
|
||||||
|
|
||||||
private ushort _titleCount;
|
private ushort _titleCount;
|
||||||
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
|
||||||
private readonly IFileSystem _fileSystem;
|
public Dvd(string path)
|
||||||
|
|
||||||
public Dvd(string path, IFileSystem fileSystem)
|
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
|
||||||
Titles = new List<Title>();
|
Titles = new List<Title>();
|
||||||
var allFiles = _fileSystem.GetFiles(path, true).ToList();
|
var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
|
||||||
|
|
||||||
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
|
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));
|
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
|
||||||
|
@ -33,7 +31,7 @@ namespace DvdLib.Ifo
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nums = ifo.Name.Split(new [] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
var nums = ifo.Name.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
if (nums.Length >= 2 && ushort.TryParse(nums[1], out var ifoNumber))
|
||||||
{
|
{
|
||||||
ReadVTS(ifoNumber, ifo.FullName);
|
ReadVTS(ifoNumber, ifo.FullName);
|
||||||
|
@ -76,7 +74,7 @@ namespace DvdLib.Ifo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
|
private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
|
||||||
{
|
{
|
||||||
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace DvdLib.Ifo
|
namespace DvdLib.Ifo
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -151,6 +152,7 @@ namespace Emby.Dlna.Api
|
||||||
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
|
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, XMLContentType, () => Task.FromResult<Stream>(new MemoryStream(bytes)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetContentDirectory request)
|
public object Get(GetContentDirectory request)
|
||||||
{
|
{
|
||||||
var xml = ContentDirectory.GetServiceXml();
|
var xml = ContentDirectory.GetServiceXml();
|
||||||
|
@ -158,6 +160,7 @@ namespace Emby.Dlna.Api
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetMediaReceiverRegistrar request)
|
public object Get(GetMediaReceiverRegistrar request)
|
||||||
{
|
{
|
||||||
var xml = MediaReceiverRegistrar.GetServiceXml();
|
var xml = MediaReceiverRegistrar.GetServiceXml();
|
||||||
|
@ -165,6 +168,7 @@ namespace Emby.Dlna.Api
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetConnnectionManager request)
|
public object Get(GetConnnectionManager request)
|
||||||
{
|
{
|
||||||
var xml = ConnectionManager.GetServiceXml();
|
var xml = ConnectionManager.GetServiceXml();
|
||||||
|
@ -313,31 +317,37 @@ namespace Emby.Dlna.Api
|
||||||
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
|
return _resultFactory.GetStaticResult(Request, cacheKey, null, cacheLength, contentType, () => Task.FromResult(_dlnaManager.GetIcon(request.Filename).Stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Subscribe(ProcessContentDirectoryEventRequest request)
|
public object Subscribe(ProcessContentDirectoryEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(ContentDirectory);
|
return ProcessEventRequest(ContentDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Subscribe(ProcessConnectionManagerEventRequest request)
|
public object Subscribe(ProcessConnectionManagerEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(ConnectionManager);
|
return ProcessEventRequest(ConnectionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
public object Subscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
|
public object Unsubscribe(ProcessContentDirectoryEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(ContentDirectory);
|
return ProcessEventRequest(ContentDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
|
public object Unsubscribe(ProcessConnectionManagerEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(ConnectionManager);
|
return ProcessEventRequest(ConnectionManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
public object Unsubscribe(ProcessMediaReceiverRegistrarEventRequest request)
|
||||||
{
|
{
|
||||||
return ProcessEventRequest(MediaReceiverRegistrar);
|
return ProcessEventRequest(MediaReceiverRegistrar);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
|
@ -52,6 +53,7 @@ namespace Emby.Dlna.Api
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetProfileInfos request)
|
public object Get(GetProfileInfos request)
|
||||||
{
|
{
|
||||||
return _dlnaManager.GetProfileInfos().ToArray();
|
return _dlnaManager.GetProfileInfos().ToArray();
|
||||||
|
@ -62,6 +64,7 @@ namespace Emby.Dlna.Api
|
||||||
return _dlnaManager.GetProfile(request.Id);
|
return _dlnaManager.GetProfile(request.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetDefaultProfile request)
|
public object Get(GetDefaultProfile request)
|
||||||
{
|
{
|
||||||
return _dlnaManager.GetDefaultProfile();
|
return _dlnaManager.GetDefaultProfile();
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -78,7 +78,18 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
_config = config;
|
_config = config;
|
||||||
|
|
||||||
_didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, accessToken, userDataManager, localization, mediaSourceManager, Logger, mediaEncoder);
|
_didlBuilder = new DidlBuilder(
|
||||||
|
profile,
|
||||||
|
user,
|
||||||
|
imageProcessor,
|
||||||
|
serverAddress,
|
||||||
|
accessToken,
|
||||||
|
userDataManager,
|
||||||
|
localization,
|
||||||
|
mediaSourceManager,
|
||||||
|
Logger,
|
||||||
|
mediaEncoder,
|
||||||
|
libraryManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -153,7 +164,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
var id = sparams["ObjectID"];
|
var id = sparams["ObjectID"];
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(id, _user);
|
var serverItem = GetItemFromObjectId(id);
|
||||||
|
|
||||||
var item = serverItem.Item;
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
@ -276,7 +287,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(id, _user);
|
var serverItem = GetItemFromObjectId(id);
|
||||||
var item = serverItem.Item;
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
|
||||||
|
@ -293,7 +304,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dlnaOptions = _config.GetDlnaConfiguration();
|
var dlnaOptions = _config.GetDlnaConfiguration();
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, item, _user, null, null, deviceId, filter);
|
_didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
provided++;
|
provided++;
|
||||||
|
@ -320,7 +331,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
|
_didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,7 +398,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
DidlBuilder.WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
var serverItem = GetItemFromObjectId(sparams["ContainerID"], _user);
|
var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
|
||||||
|
|
||||||
var item = serverItem.Item;
|
var item = serverItem.Item;
|
||||||
|
|
||||||
|
@ -406,7 +417,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_didlBuilder.WriteItemElement(dlnaOptions, writer, i, _user, item, serverItem.StubType, deviceId, filter);
|
_didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,11 +523,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetFolders(item, user, stubType, sort, startIndex, limit);
|
return GetFolders(user, startIndex, limit);
|
||||||
}
|
}
|
||||||
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return GetLiveTvChannels(item, user, stubType, sort, startIndex, limit);
|
return GetLiveTvChannels(user, sort, startIndex, limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +558,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return ToResult(queryResult);
|
return ToResult(queryResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetLiveTvChannels(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var query = new InternalItemsQuery(user)
|
var query = new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
|
@ -579,7 +590,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.Playlists)
|
if (stubType.HasValue && stubType.Value == StubType.Playlists)
|
||||||
{
|
{
|
||||||
return GetMusicPlaylists(item, user, query);
|
return GetMusicPlaylists(user, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.Albums)
|
if (stubType.HasValue && stubType.Value == StubType.Albums)
|
||||||
|
@ -707,7 +718,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.Collections)
|
if (stubType.HasValue && stubType.Value == StubType.Collections)
|
||||||
{
|
{
|
||||||
return GetMovieCollections(item, user, query);
|
return GetMovieCollections(user, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.Favorites)
|
if (stubType.HasValue && stubType.Value == StubType.Favorites)
|
||||||
|
@ -720,46 +731,42 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return GetGenres(item, user, query);
|
return GetGenres(item, user, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<ServerItem>();
|
var array = new ServerItem[]
|
||||||
|
|
||||||
list.Add(new ServerItem(item)
|
|
||||||
{
|
{
|
||||||
StubType = StubType.ContinueWatching
|
new ServerItem(item)
|
||||||
});
|
{
|
||||||
|
StubType = StubType.ContinueWatching
|
||||||
list.Add(new ServerItem(item)
|
},
|
||||||
{
|
new ServerItem(item)
|
||||||
StubType = StubType.Latest
|
{
|
||||||
});
|
StubType = StubType.Latest
|
||||||
|
},
|
||||||
list.Add(new ServerItem(item)
|
new ServerItem(item)
|
||||||
{
|
{
|
||||||
StubType = StubType.Movies
|
StubType = StubType.Movies
|
||||||
});
|
},
|
||||||
|
new ServerItem(item)
|
||||||
list.Add(new ServerItem(item)
|
{
|
||||||
{
|
StubType = StubType.Collections
|
||||||
StubType = StubType.Collections
|
},
|
||||||
});
|
new ServerItem(item)
|
||||||
|
{
|
||||||
list.Add(new ServerItem(item)
|
StubType = StubType.Favorites
|
||||||
{
|
},
|
||||||
StubType = StubType.Favorites
|
new ServerItem(item)
|
||||||
});
|
{
|
||||||
|
StubType = StubType.Genres
|
||||||
list.Add(new ServerItem(item)
|
}
|
||||||
{
|
};
|
||||||
StubType = StubType.Genres
|
|
||||||
});
|
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = list,
|
Items = array,
|
||||||
TotalRecordCount = list.Count
|
TotalRecordCount = array.Length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit)
|
||||||
{
|
{
|
||||||
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||||
.OrderBy(i => i.SortName)
|
.OrderBy(i => i.SortName)
|
||||||
|
@ -792,7 +799,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.NextUp)
|
if (stubType.HasValue && stubType.Value == StubType.NextUp)
|
||||||
{
|
{
|
||||||
return GetNextUp(item, user, query);
|
return GetNextUp(item, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stubType.HasValue && stubType.Value == StubType.Latest)
|
if (stubType.HasValue && stubType.Value == StubType.Latest)
|
||||||
|
@ -910,7 +917,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return ToResult(result);
|
return ToResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetMovieCollections(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Recursive = true;
|
query.Recursive = true;
|
||||||
//query.Parent = parent;
|
//query.Parent = parent;
|
||||||
|
@ -1105,7 +1112,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return ToResult(result);
|
return ToResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetMusicPlaylists(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.Parent = null;
|
query.Parent = null;
|
||||||
query.IncludeItemTypes = new[] { typeof(Playlist).Name };
|
query.IncludeItemTypes = new[] { typeof(Playlist).Name };
|
||||||
|
@ -1134,7 +1141,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return ToResult(items);
|
return ToResult(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetNextUp(BaseItem parent, User user, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
||||||
|
|
||||||
|
@ -1289,15 +1296,15 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerItem GetItemFromObjectId(string id, User user)
|
private ServerItem GetItemFromObjectId(string id)
|
||||||
{
|
{
|
||||||
return DidlBuilder.IsIdRoot(id)
|
return DidlBuilder.IsIdRoot(id)
|
||||||
|
|
||||||
? new ServerItem(_libraryManager.GetUserRootFolder())
|
? new ServerItem(_libraryManager.GetUserRootFolder())
|
||||||
: ParseItemId(id, user);
|
: ParseItemId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerItem ParseItemId(string id, User user)
|
private ServerItem ParseItemId(string id)
|
||||||
{
|
{
|
||||||
StubType? stubType = null;
|
StubType? stubType = null;
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ namespace Emby.Dlna.Didl
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public DidlBuilder(
|
public DidlBuilder(
|
||||||
DeviceProfile profile,
|
DeviceProfile profile,
|
||||||
|
@ -56,7 +57,8 @@ namespace Emby.Dlna.Didl
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IMediaEncoder mediaEncoder)
|
IMediaEncoder mediaEncoder,
|
||||||
|
ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_profile = profile;
|
_profile = profile;
|
||||||
_user = user;
|
_user = user;
|
||||||
|
@ -68,6 +70,7 @@ namespace Emby.Dlna.Didl
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeDlnaMediaUrl(string url)
|
public static string NormalizeDlnaMediaUrl(string url)
|
||||||
|
@ -75,7 +78,7 @@ namespace Emby.Dlna.Didl
|
||||||
return url + "&dlnaheaders=true";
|
return url + "&dlnaheaders=true";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetItemDidl(DlnaOptions options, BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
|
public string GetItemDidl(BaseItem item, User user, BaseItem context, string deviceId, Filter filter, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
@ -100,7 +103,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
WriteXmlRootAttributes(_profile, writer);
|
WriteXmlRootAttributes(_profile, writer);
|
||||||
|
|
||||||
WriteItemElement(options, writer, item, user, context, null, deviceId, filter, streamInfo);
|
WriteItemElement(writer, item, user, context, null, deviceId, filter, streamInfo);
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
//writer.WriteEndDocument();
|
//writer.WriteEndDocument();
|
||||||
|
@ -127,7 +130,6 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteItemElement(
|
public void WriteItemElement(
|
||||||
DlnaOptions options,
|
|
||||||
XmlWriter writer,
|
XmlWriter writer,
|
||||||
BaseItem item,
|
BaseItem item,
|
||||||
User user,
|
User user,
|
||||||
|
@ -164,25 +166,23 @@ namespace Emby.Dlna.Didl
|
||||||
// refID?
|
// refID?
|
||||||
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
// storeAttribute(itemNode, object, ClassProperties.REF_ID, false);
|
||||||
|
|
||||||
var hasMediaSources = item as IHasMediaSources;
|
if (item is IHasMediaSources)
|
||||||
|
|
||||||
if (hasMediaSources != null)
|
|
||||||
{
|
{
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
AddAudioResource(options, writer, item, deviceId, filter, streamInfo);
|
AddAudioResource(writer, item, deviceId, filter, streamInfo);
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
AddVideoResource(options, writer, item, deviceId, filter, streamInfo);
|
AddVideoResource(writer, item, deviceId, filter, streamInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AddCover(item, context, null, writer);
|
AddCover(item, null, writer);
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddVideoResource(DlnaOptions options, XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
if (streamInfo == null)
|
if (streamInfo == null)
|
||||||
{
|
{
|
||||||
|
@ -226,7 +226,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
foreach (var contentFeature in contentFeatureList)
|
foreach (var contentFeature in contentFeatureList)
|
||||||
{
|
{
|
||||||
AddVideoResource(writer, video, deviceId, filter, contentFeature, streamInfo);
|
AddVideoResource(writer, filter, contentFeature, streamInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
|
var subtitleProfiles = streamInfo.GetSubtitleProfiles(_mediaEncoder, false, _serverAddress, _accessToken);
|
||||||
|
@ -283,7 +283,10 @@ namespace Emby.Dlna.Didl
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||||
var protocolInfo = string.Format("http-get:*:text/{0}:*", info.Format.ToLowerInvariant());
|
var protocolInfo = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"http-get:*:text/{0}:*",
|
||||||
|
info.Format.ToLowerInvariant());
|
||||||
writer.WriteAttributeString("protocolInfo", protocolInfo);
|
writer.WriteAttributeString("protocolInfo", protocolInfo);
|
||||||
|
|
||||||
writer.WriteString(info.Url);
|
writer.WriteString(info.Url);
|
||||||
|
@ -293,7 +296,7 @@ namespace Emby.Dlna.Didl
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddVideoResource(XmlWriter writer, BaseItem video, string deviceId, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
private void AddVideoResource(XmlWriter writer, Filter filter, string contentFeatures, StreamInfo streamInfo)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||||
|
|
||||||
|
@ -335,7 +338,13 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
if (targetWidth.HasValue && targetHeight.HasValue)
|
if (targetWidth.HasValue && targetHeight.HasValue)
|
||||||
{
|
{
|
||||||
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", targetWidth.Value, targetHeight.Value));
|
writer.WriteAttributeString(
|
||||||
|
"resolution",
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}x{1}",
|
||||||
|
targetWidth.Value,
|
||||||
|
targetHeight.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -369,17 +378,19 @@ namespace Emby.Dlna.Didl
|
||||||
streamInfo.TargetVideoCodecTag,
|
streamInfo.TargetVideoCodecTag,
|
||||||
streamInfo.IsTargetAVC);
|
streamInfo.IsTargetAVC);
|
||||||
|
|
||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? MimeTypes.GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
: mediaProfile.MimeType;
|
: mediaProfile.MimeType;
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString(
|
||||||
"http-get:*:{0}:{1}",
|
"protocolInfo",
|
||||||
mimeType,
|
string.Format(
|
||||||
contentFeatures
|
CultureInfo.InvariantCulture,
|
||||||
));
|
"http-get:*:{0}:{1}",
|
||||||
|
mimeType,
|
||||||
|
contentFeatures));
|
||||||
|
|
||||||
writer.WriteString(url);
|
writer.WriteString(url);
|
||||||
|
|
||||||
|
@ -392,54 +403,122 @@ namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
switch (itemStubType.Value)
|
switch (itemStubType.Value)
|
||||||
{
|
{
|
||||||
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
case StubType.Latest: return _localization.GetLocalizedString("Latest");
|
||||||
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
case StubType.Playlists: return _localization.GetLocalizedString("Playlists");
|
||||||
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
case StubType.AlbumArtists: return _localization.GetLocalizedString("HeaderAlbumArtists");
|
||||||
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
case StubType.Albums: return _localization.GetLocalizedString("Albums");
|
||||||
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
case StubType.Artists: return _localization.GetLocalizedString("Artists");
|
||||||
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
case StubType.Songs: return _localization.GetLocalizedString("Songs");
|
||||||
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
case StubType.Genres: return _localization.GetLocalizedString("Genres");
|
||||||
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
case StubType.FavoriteAlbums: return _localization.GetLocalizedString("HeaderFavoriteAlbums");
|
||||||
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
case StubType.FavoriteArtists: return _localization.GetLocalizedString("HeaderFavoriteArtists");
|
||||||
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
case StubType.FavoriteSongs: return _localization.GetLocalizedString("HeaderFavoriteSongs");
|
||||||
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
|
case StubType.ContinueWatching: return _localization.GetLocalizedString("HeaderContinueWatching");
|
||||||
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
case StubType.Movies: return _localization.GetLocalizedString("Movies");
|
||||||
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
case StubType.Collections: return _localization.GetLocalizedString("Collections");
|
||||||
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
case StubType.Favorites: return _localization.GetLocalizedString("Favorites");
|
||||||
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
case StubType.NextUp: return _localization.GetLocalizedString("HeaderNextUp");
|
||||||
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
case StubType.FavoriteSeries: return _localization.GetLocalizedString("HeaderFavoriteShows");
|
||||||
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
case StubType.FavoriteEpisodes: return _localization.GetLocalizedString("HeaderFavoriteEpisodes");
|
||||||
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
case StubType.Series: return _localization.GetLocalizedString("Shows");
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is Episode episode && context is Season season)
|
return item is Episode episode
|
||||||
|
? GetEpisodeDisplayName(episode, context)
|
||||||
|
: item.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets episode display name appropriate for the given context.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If context is a season, this will return a string containing just episode number and name.
|
||||||
|
/// Otherwise the result will include series nams and season number.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <param name="context">Current context.</param>
|
||||||
|
/// <returns>Formatted name of the episode.</returns>
|
||||||
|
private string GetEpisodeDisplayName(Episode episode, BaseItem context)
|
||||||
|
{
|
||||||
|
string[] components;
|
||||||
|
|
||||||
|
if (context is Season season)
|
||||||
{
|
{
|
||||||
// This is a special embedded within a season
|
// This is a special embedded within a season
|
||||||
if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
|
if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
|
||||||
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
|
||||||
{
|
{
|
||||||
return string.Format(_localization.GetLocalizedString("ValueSpecialEpisodeName"), item.Name);
|
return string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
|
||||||
|
episode.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.IndexNumber.HasValue)
|
// inside a season use simple format (ex. '12 - Episode Name')
|
||||||
|
var epNumberName = GetEpisodeIndexFullName(episode);
|
||||||
|
components = new[] { epNumberName, episode.Name };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
|
||||||
|
var epNumberName = GetEpisodeNumberDisplayName(episode);
|
||||||
|
components = new[] { episode.SeriesName, epNumberName, episode.Name };
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets complete episode number.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <returns>For single episodes returns just the number. For double episodes - current and ending numbers.</returns>
|
||||||
|
private string GetEpisodeIndexFullName(Episode episode)
|
||||||
|
{
|
||||||
|
var name = string.Empty;
|
||||||
|
if (episode.IndexNumber.HasValue)
|
||||||
|
{
|
||||||
|
name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
if (episode.IndexNumberEnd.HasValue)
|
||||||
{
|
{
|
||||||
var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (episode.IndexNumberEnd.HasValue)
|
|
||||||
{
|
|
||||||
number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
return number + " - " + item.Name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.Name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddAudioResource(DlnaOptions options, XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
/// <summary>
|
||||||
|
/// Gets episode number formatted as 'S##E##'.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episode">The episode.</param>
|
||||||
|
/// <returns>Formatted episode number.</returns>
|
||||||
|
private string GetEpisodeNumberDisplayName(Episode episode)
|
||||||
|
{
|
||||||
|
var name = string.Empty;
|
||||||
|
var seasonNumber = episode.Season?.IndexNumber;
|
||||||
|
|
||||||
|
if (seasonNumber.HasValue)
|
||||||
|
{
|
||||||
|
name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
var indexName = GetEpisodeIndexFullName(episode);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(indexName))
|
||||||
|
{
|
||||||
|
name += "E" + indexName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
|
||||||
|
|
||||||
|
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
|
||||||
{
|
{
|
||||||
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
|
||||||
|
|
||||||
|
@ -505,7 +584,7 @@ namespace Emby.Dlna.Didl
|
||||||
targetSampleRate,
|
targetSampleRate,
|
||||||
targetAudioBitDepth);
|
targetAudioBitDepth);
|
||||||
|
|
||||||
var filename = url.Substring(0, url.IndexOf('?'));
|
var filename = url.Substring(0, url.IndexOf('?', StringComparison.Ordinal));
|
||||||
|
|
||||||
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
var mimeType = mediaProfile == null || string.IsNullOrEmpty(mediaProfile.MimeType)
|
||||||
? MimeTypes.GetMimeType(filename)
|
? MimeTypes.GetMimeType(filename)
|
||||||
|
@ -521,11 +600,13 @@ namespace Emby.Dlna.Didl
|
||||||
streamInfo.RunTimeTicks ?? 0,
|
streamInfo.RunTimeTicks ?? 0,
|
||||||
streamInfo.TranscodeSeekInfo);
|
streamInfo.TranscodeSeekInfo);
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString(
|
||||||
"http-get:*:{0}:{1}",
|
"protocolInfo",
|
||||||
mimeType,
|
string.Format(
|
||||||
contentFeatures
|
CultureInfo.InvariantCulture,
|
||||||
));
|
"http-get:*:{0}:{1}",
|
||||||
|
mimeType,
|
||||||
|
contentFeatures));
|
||||||
|
|
||||||
writer.WriteString(url);
|
writer.WriteString(url);
|
||||||
|
|
||||||
|
@ -548,7 +629,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
var clientId = GetClientId(folder, stubType);
|
var clientId = GetClientId(folder, stubType);
|
||||||
|
|
||||||
if (string.Equals(requestedId, "0"))
|
if (string.Equals(requestedId, "0", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
writer.WriteAttributeString("id", "0");
|
writer.WriteAttributeString("id", "0");
|
||||||
writer.WriteAttributeString("parentID", "-1");
|
writer.WriteAttributeString("parentID", "-1");
|
||||||
|
@ -577,7 +658,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
AddGeneralProperties(folder, stubType, context, writer, filter);
|
AddGeneralProperties(folder, stubType, context, writer, filter);
|
||||||
|
|
||||||
AddCover(folder, context, stubType, writer);
|
AddCover(folder, stubType, writer);
|
||||||
|
|
||||||
writer.WriteFullEndElement();
|
writer.WriteFullEndElement();
|
||||||
}
|
}
|
||||||
|
@ -610,7 +691,10 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (playbackPositionTicks > 0)
|
if (playbackPositionTicks > 0)
|
||||||
{
|
{
|
||||||
var elementValue = string.Format("BM={0}", Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds).ToString(_usCulture));
|
var elementValue = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"BM={0}",
|
||||||
|
Convert.ToInt32(TimeSpan.FromTicks(playbackPositionTicks).TotalSeconds));
|
||||||
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
AddValue(writer, "sec", "dcmInfo", elementValue, secAttribute.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -763,37 +847,36 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private void AddPeople(BaseItem item, XmlWriter writer)
|
private void AddPeople(BaseItem item, XmlWriter writer)
|
||||||
{
|
{
|
||||||
//var types = new[]
|
if (!item.SupportsPeople)
|
||||||
//{
|
{
|
||||||
// PersonType.Director,
|
return;
|
||||||
// PersonType.Writer,
|
}
|
||||||
// PersonType.Producer,
|
|
||||||
// PersonType.Composer,
|
|
||||||
// "Creator"
|
|
||||||
//};
|
|
||||||
|
|
||||||
//var people = _libraryManager.GetPeople(item);
|
var types = new[]
|
||||||
|
{
|
||||||
|
PersonType.Director,
|
||||||
|
PersonType.Writer,
|
||||||
|
PersonType.Producer,
|
||||||
|
PersonType.Composer,
|
||||||
|
"creator"
|
||||||
|
};
|
||||||
|
|
||||||
//var index = 0;
|
// Seeing some LG models locking up due content with large lists of people
|
||||||
|
// The actual issue might just be due to processing a more metadata than it can handle
|
||||||
|
var people = _libraryManager.GetPeople(
|
||||||
|
new InternalPeopleQuery
|
||||||
|
{
|
||||||
|
ItemId = item.Id,
|
||||||
|
Limit = 6
|
||||||
|
});
|
||||||
|
|
||||||
//// Seeing some LG models locking up due content with large lists of people
|
foreach (var actor in people)
|
||||||
//// The actual issue might just be due to processing a more metadata than it can handle
|
{
|
||||||
//var limit = 6;
|
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?? PersonType.Actor;
|
||||||
|
|
||||||
//foreach (var actor in people)
|
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
||||||
//{
|
}
|
||||||
// var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// ?? PersonType.Actor;
|
|
||||||
|
|
||||||
// AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NS_UPNP);
|
|
||||||
|
|
||||||
// index++;
|
|
||||||
|
|
||||||
// if (index >= limit)
|
|
||||||
// {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
|
private void AddGeneralProperties(BaseItem item, StubType? itemStubType, BaseItem context, XmlWriter writer, Filter filter)
|
||||||
|
@ -870,7 +953,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlWriter writer)
|
private void AddCover(BaseItem item, StubType? stubType, XmlWriter writer)
|
||||||
{
|
{
|
||||||
ImageDownloadInfo imageInfo = GetImageInfo(item);
|
ImageDownloadInfo imageInfo = GetImageInfo(item);
|
||||||
|
|
||||||
|
@ -915,17 +998,8 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEmbeddedImageAsCover(string name, XmlWriter writer)
|
private void AddImageResElement(
|
||||||
{
|
BaseItem item,
|
||||||
writer.WriteStartElement("upnp", "albumArtURI", NS_UPNP);
|
|
||||||
writer.WriteAttributeString("dlna", "profileID", NS_DLNA, _profile.AlbumArtPn);
|
|
||||||
writer.WriteString(_serverAddress + "/Dlna/icons/people480.jpg");
|
|
||||||
writer.WriteFullEndElement();
|
|
||||||
|
|
||||||
writer.WriteElementString("upnp", "icon", NS_UPNP, _serverAddress + "/Dlna/icons/people48.jpg");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddImageResElement(BaseItem item,
|
|
||||||
XmlWriter writer,
|
XmlWriter writer,
|
||||||
int maxWidth,
|
int maxWidth,
|
||||||
int maxHeight,
|
int maxHeight,
|
||||||
|
@ -951,13 +1025,17 @@ namespace Emby.Dlna.Didl
|
||||||
var contentFeatures = new ContentFeatureBuilder(_profile)
|
var contentFeatures = new ContentFeatureBuilder(_profile)
|
||||||
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
|
.BuildImageHeader(format, width, height, imageInfo.IsDirectStream, org_Pn);
|
||||||
|
|
||||||
writer.WriteAttributeString("protocolInfo", string.Format(
|
writer.WriteAttributeString(
|
||||||
"http-get:*:{0}:{1}",
|
"protocolInfo",
|
||||||
MimeTypes.GetMimeType("file." + format),
|
string.Format(
|
||||||
contentFeatures
|
CultureInfo.InvariantCulture,
|
||||||
));
|
"http-get:*:{0}:{1}",
|
||||||
|
MimeTypes.GetMimeType("file." + format),
|
||||||
|
contentFeatures));
|
||||||
|
|
||||||
writer.WriteAttributeString("resolution", string.Format("{0}x{1}", width, height));
|
writer.WriteAttributeString(
|
||||||
|
"resolution",
|
||||||
|
string.Format(CultureInfo.InvariantCulture, "{0}x{1}", width, height));
|
||||||
|
|
||||||
writer.WriteString(albumartUrlInfo.Url);
|
writer.WriteString(albumartUrlInfo.Url);
|
||||||
|
|
||||||
|
@ -982,19 +1060,58 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
|
// For audio tracks without art use album art if available.
|
||||||
|
if (item is Audio audioItem)
|
||||||
if (item != null)
|
|
||||||
{
|
{
|
||||||
if (item.HasImage(ImageType.Primary))
|
var album = audioItem.AlbumEntity;
|
||||||
{
|
return album != null && album.HasImage(ImageType.Primary)
|
||||||
return GetImageInfo(item, ImageType.Primary);
|
? GetImageInfo(album, ImageType.Primary)
|
||||||
}
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
|
||||||
|
if (item is MusicAlbum || item is Playlist)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
|
||||||
|
var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
|
||||||
|
if (parentWithImage != null)
|
||||||
|
{
|
||||||
|
return GetImageInfo(parentWithImage, ImageType.Primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
|
||||||
|
{
|
||||||
|
if (item == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.HasImage(ImageType.Primary))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parent = item.GetParent();
|
||||||
|
if (parent is UserRootFolder)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// terminate in case we went past user root folder (unlikely?)
|
||||||
|
if (parent is Folder folder && folder.IsRoot)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetFirstParentWithImageBelowUserRoot(parent);
|
||||||
|
}
|
||||||
|
|
||||||
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
|
||||||
{
|
{
|
||||||
var imageInfo = item.GetImageInfo(type, 0);
|
var imageInfo = item.GetImageInfo(type, 0);
|
||||||
|
@ -1096,7 +1213,9 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format)
|
||||||
{
|
{
|
||||||
var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
var url = string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/0/0",
|
||||||
_serverAddress,
|
_serverAddress,
|
||||||
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
info.ItemId.ToString("N", CultureInfo.InvariantCulture),
|
||||||
info.Type,
|
info.Type,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Didl
|
namespace Emby.Dlna.Didl
|
||||||
{
|
{
|
||||||
|
|
|
@ -53,6 +53,6 @@ namespace Emby.Dlna.Didl
|
||||||
_encoding = encoding;
|
_encoding = encoding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Encoding Encoding => (null == _encoding) ? base.Encoding : _encoding;
|
public override Encoding Encoding => _encoding ?? base.Encoding;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{805844AB-E92F-45E6-9D99-4F6D48D129A5}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="..\SharedVersion.cs" />
|
<Compile Include="..\SharedVersion.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -262,8 +262,8 @@ namespace Emby.Dlna.Main
|
||||||
{
|
{
|
||||||
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
if (address.AddressFamily == AddressFamily.InterNetworkV6)
|
||||||
{
|
{
|
||||||
// Not support IPv6 right now
|
// Not supporting IPv6 right now
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
|
@ -346,7 +346,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
throw new InvalidOperationException("Unable to find service");
|
throw new InvalidOperationException("Unable to find service");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1));
|
return new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
avCommands.BuildPost(command, service.ServiceType, 1),
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetPlay(CancellationToken cancellationToken)
|
public async Task SetPlay(CancellationToken cancellationToken)
|
||||||
|
@ -515,8 +520,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
.ConfigureAwait(false);
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
rendererCommands.BuildPost(command, service.ServiceType),
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
{
|
||||||
|
@ -561,8 +570,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), true)
|
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
.ConfigureAwait(false);
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
rendererCommands.BuildPost(command, service.ServiceType),
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
return;
|
return;
|
||||||
|
@ -588,8 +601,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
.ConfigureAwait(false);
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
avCommands.BuildPost(command, service.ServiceType),
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
{
|
||||||
|
@ -599,7 +616,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
var transportState =
|
var transportState =
|
||||||
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
|
result.Document.Descendants(uPnpNamespaces.AvTransport + "GetTransportInfoResponse").Select(i => i.Element("CurrentTransportState")).FirstOrDefault(i => i != null);
|
||||||
|
|
||||||
var transportStateValue = transportState == null ? null : transportState.Value;
|
var transportStateValue = transportState?.Value;
|
||||||
|
|
||||||
if (transportStateValue != null
|
if (transportStateValue != null
|
||||||
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
&& Enum.TryParse(transportStateValue, true, out TRANSPORTSTATE state))
|
||||||
|
@ -626,8 +643,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
.ConfigureAwait(false);
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
rendererCommands.BuildPost(command, service.ServiceType),
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
{
|
||||||
|
@ -689,8 +710,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
var rendererCommands = await GetRenderingProtocolAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType), false)
|
var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(
|
||||||
.ConfigureAwait(false);
|
Properties.BaseUrl,
|
||||||
|
service,
|
||||||
|
command.Name,
|
||||||
|
rendererCommands.BuildPost(command, service.ServiceType),
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (result == null || result.Document == null)
|
if (result == null || result.Document == null)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlayToController : ISessionController, IDisposable
|
public class PlayToController : ISessionController, IDisposable
|
||||||
{
|
{
|
||||||
|
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
||||||
|
|
||||||
private Device _device;
|
private Device _device;
|
||||||
private readonly SessionInfo _session;
|
private readonly SessionInfo _session;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
|
@ -45,9 +47,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly string _serverAddress;
|
private readonly string _serverAddress;
|
||||||
private readonly string _accessToken;
|
private readonly string _accessToken;
|
||||||
|
|
||||||
public bool IsSessionActive => !_disposed && _device != null;
|
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
||||||
|
private int _currentPlaylistIndex;
|
||||||
|
|
||||||
public bool SupportsMediaControl => IsSessionActive;
|
private bool _disposed;
|
||||||
|
|
||||||
public PlayToController(
|
public PlayToController(
|
||||||
SessionInfo session,
|
SessionInfo session,
|
||||||
|
@ -83,18 +86,22 @@ namespace Emby.Dlna.PlayTo
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsSessionActive => !_disposed && _device != null;
|
||||||
|
|
||||||
|
public bool SupportsMediaControl => IsSessionActive;
|
||||||
|
|
||||||
public void Init(Device device)
|
public void Init(Device device)
|
||||||
{
|
{
|
||||||
_device = device;
|
_device = device;
|
||||||
_device.OnDeviceUnavailable = OnDeviceUnavailable;
|
_device.OnDeviceUnavailable = OnDeviceUnavailable;
|
||||||
_device.PlaybackStart += _device_PlaybackStart;
|
_device.PlaybackStart += OnDevicePlaybackStart;
|
||||||
_device.PlaybackProgress += _device_PlaybackProgress;
|
_device.PlaybackProgress += OnDevicePlaybackProgress;
|
||||||
_device.PlaybackStopped += _device_PlaybackStopped;
|
_device.PlaybackStopped += OnDevicePlaybackStopped;
|
||||||
_device.MediaChanged += _device_MediaChanged;
|
_device.MediaChanged += OnDeviceMediaChanged;
|
||||||
|
|
||||||
_device.Start();
|
_device.Start();
|
||||||
|
|
||||||
_deviceDiscovery.DeviceLeft += _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft += OnDeviceDiscoveryDeviceLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDeviceUnavailable()
|
private void OnDeviceUnavailable()
|
||||||
|
@ -110,7 +117,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
private void OnDeviceDiscoveryDeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
|
||||||
{
|
{
|
||||||
var info = e.Argument;
|
var info = e.Argument;
|
||||||
|
|
||||||
|
@ -125,7 +132,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _device_MediaChanged(object sender, MediaChangedEventArgs e)
|
private async void OnDeviceMediaChanged(object sender, MediaChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -137,15 +144,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
var streamInfo = StreamParams.ParseFromUrl(e.OldMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
||||||
if (streamInfo.Item != null)
|
if (streamInfo.Item != null)
|
||||||
{
|
{
|
||||||
var positionTicks = GetProgressPositionTicks(e.OldMediaInfo, streamInfo);
|
var positionTicks = GetProgressPositionTicks(streamInfo);
|
||||||
|
|
||||||
ReportPlaybackStopped(e.OldMediaInfo, streamInfo, positionTicks);
|
ReportPlaybackStopped(streamInfo, positionTicks);
|
||||||
}
|
}
|
||||||
|
|
||||||
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
streamInfo = StreamParams.ParseFromUrl(e.NewMediaInfo.Url, _libraryManager, _mediaSourceManager);
|
||||||
if (streamInfo.Item == null) return;
|
if (streamInfo.Item == null) return;
|
||||||
|
|
||||||
var newItemProgress = GetProgressInfo(e.NewMediaInfo, streamInfo);
|
var newItemProgress = GetProgressInfo(streamInfo);
|
||||||
|
|
||||||
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
await _sessionManager.OnPlaybackStart(newItemProgress).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -155,7 +162,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _device_PlaybackStopped(object sender, PlaybackStoppedEventArgs e)
|
private async void OnDevicePlaybackStopped(object sender, PlaybackStoppedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -168,9 +175,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (streamInfo.Item == null) return;
|
if (streamInfo.Item == null) return;
|
||||||
|
|
||||||
var positionTicks = GetProgressPositionTicks(e.MediaInfo, streamInfo);
|
var positionTicks = GetProgressPositionTicks(streamInfo);
|
||||||
|
|
||||||
ReportPlaybackStopped(e.MediaInfo, streamInfo, positionTicks);
|
ReportPlaybackStopped(streamInfo, positionTicks);
|
||||||
|
|
||||||
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -194,7 +201,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Playlist.Clear();
|
_playlist.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -203,7 +210,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void ReportPlaybackStopped(uBaseObject mediaInfo, StreamParams streamInfo, long? positionTicks)
|
private async void ReportPlaybackStopped(StreamParams streamInfo, long? positionTicks)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -222,7 +229,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _device_PlaybackStart(object sender, PlaybackStartEventArgs e)
|
private async void OnDevicePlaybackStart(object sender, PlaybackStartEventArgs e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -235,7 +242,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (info.Item != null)
|
if (info.Item != null)
|
||||||
{
|
{
|
||||||
var progress = GetProgressInfo(e.MediaInfo, info);
|
var progress = GetProgressInfo(info);
|
||||||
|
|
||||||
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
|
await _sessionManager.OnPlaybackStart(progress).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -246,7 +253,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async void _device_PlaybackProgress(object sender, PlaybackProgressEventArgs e)
|
private async void OnDevicePlaybackProgress(object sender, PlaybackProgressEventArgs e)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -266,7 +273,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (info.Item != null)
|
if (info.Item != null)
|
||||||
{
|
{
|
||||||
var progress = GetProgressInfo(e.MediaInfo, info);
|
var progress = GetProgressInfo(info);
|
||||||
|
|
||||||
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
|
await _sessionManager.OnPlaybackProgress(progress).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
@ -277,7 +284,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private long? GetProgressPositionTicks(uBaseObject mediaInfo, StreamParams info)
|
private long? GetProgressPositionTicks(StreamParams info)
|
||||||
{
|
{
|
||||||
var ticks = _device.Position.Ticks;
|
var ticks = _device.Position.Ticks;
|
||||||
|
|
||||||
|
@ -289,13 +296,13 @@ namespace Emby.Dlna.PlayTo
|
||||||
return ticks;
|
return ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackStartInfo GetProgressInfo(uBaseObject mediaInfo, StreamParams info)
|
private PlaybackStartInfo GetProgressInfo(StreamParams info)
|
||||||
{
|
{
|
||||||
return new PlaybackStartInfo
|
return new PlaybackStartInfo
|
||||||
{
|
{
|
||||||
ItemId = info.ItemId,
|
ItemId = info.ItemId,
|
||||||
SessionId = _session.Id,
|
SessionId = _session.Id,
|
||||||
PositionTicks = GetProgressPositionTicks(mediaInfo, info),
|
PositionTicks = GetProgressPositionTicks(info),
|
||||||
IsMuted = _device.IsMuted,
|
IsMuted = _device.IsMuted,
|
||||||
IsPaused = _device.IsPaused,
|
IsPaused = _device.IsPaused,
|
||||||
MediaSourceId = info.MediaSourceId,
|
MediaSourceId = info.MediaSourceId,
|
||||||
|
@ -310,9 +317,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#region SendCommands
|
public Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
||||||
|
|
||||||
public async Task SendPlayCommand(PlayRequest command, CancellationToken cancellationToken)
|
|
||||||
{
|
{
|
||||||
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
|
_logger.LogDebug("{0} - Received PlayRequest: {1}", this._session.DeviceName, command.PlayCommand);
|
||||||
|
|
||||||
|
@ -350,11 +355,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (command.PlayCommand == PlayCommand.PlayLast)
|
if (command.PlayCommand == PlayCommand.PlayLast)
|
||||||
{
|
{
|
||||||
Playlist.AddRange(playlist);
|
_playlist.AddRange(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.PlayCommand == PlayCommand.PlayNext)
|
if (command.PlayCommand == PlayCommand.PlayNext)
|
||||||
{
|
{
|
||||||
Playlist.AddRange(playlist);
|
_playlist.AddRange(playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!command.ControllingUserId.Equals(Guid.Empty))
|
if (!command.ControllingUserId.Equals(Guid.Empty))
|
||||||
|
@ -363,7 +369,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_session.DeviceName, _session.RemoteEndPoint, user);
|
_session.DeviceName, _session.RemoteEndPoint, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
await PlayItems(playlist).ConfigureAwait(false);
|
return PlayItems(playlist, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
|
private Task SendPlaystateCommand(PlaystateRequest command, CancellationToken cancellationToken)
|
||||||
|
@ -371,7 +377,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
switch (command.Command)
|
switch (command.Command)
|
||||||
{
|
{
|
||||||
case PlaystateCommand.Stop:
|
case PlaystateCommand.Stop:
|
||||||
Playlist.Clear();
|
_playlist.Clear();
|
||||||
return _device.SetStop(CancellationToken.None);
|
return _device.SetStop(CancellationToken.None);
|
||||||
|
|
||||||
case PlaystateCommand.Pause:
|
case PlaystateCommand.Pause:
|
||||||
|
@ -387,10 +393,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
return Seek(command.SeekPositionTicks ?? 0);
|
return Seek(command.SeekPositionTicks ?? 0);
|
||||||
|
|
||||||
case PlaystateCommand.NextTrack:
|
case PlaystateCommand.NextTrack:
|
||||||
return SetPlaylistIndex(_currentPlaylistIndex + 1);
|
return SetPlaylistIndex(_currentPlaylistIndex + 1, cancellationToken);
|
||||||
|
|
||||||
case PlaystateCommand.PreviousTrack:
|
case PlaystateCommand.PreviousTrack:
|
||||||
return SetPlaylistIndex(_currentPlaylistIndex - 1);
|
return SetPlaylistIndex(_currentPlaylistIndex - 1, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -426,14 +432,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
return info.IsDirectStream;
|
return info.IsDirectStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Playlist
|
|
||||||
|
|
||||||
private int _currentPlaylistIndex;
|
|
||||||
private readonly List<PlaylistItem> _playlist = new List<PlaylistItem>();
|
|
||||||
private List<PlaylistItem> Playlist => _playlist;
|
|
||||||
|
|
||||||
private void AddItemFromId(Guid id, List<BaseItem> list)
|
private void AddItemFromId(Guid id, List<BaseItem> list)
|
||||||
{
|
{
|
||||||
var item = _libraryManager.GetItemById(id);
|
var item = _libraryManager.GetItemById(id);
|
||||||
|
@ -451,7 +449,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
_dlnaManager.GetDefaultProfile();
|
_dlnaManager.GetDefaultProfile();
|
||||||
|
|
||||||
var mediaSources = item is IHasMediaSources
|
var mediaSources = item is IHasMediaSources
|
||||||
? (_mediaSourceManager.GetStaticMediaSources(item, true, user))
|
? _mediaSourceManager.GetStaticMediaSources(item, true, user)
|
||||||
: new List<MediaSourceInfo>();
|
: new List<MediaSourceInfo>();
|
||||||
|
|
||||||
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
var playlistItem = GetPlaylistItem(item, mediaSources, profile, _session.DeviceId, mediaSourceId, audioStreamIndex, subtitleStreamIndex);
|
||||||
|
@ -459,8 +457,19 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
|
playlistItem.StreamUrl = DidlBuilder.NormalizeDlnaMediaUrl(playlistItem.StreamInfo.ToUrl(_serverAddress, _accessToken));
|
||||||
|
|
||||||
var itemXml = new DidlBuilder(profile, user, _imageProcessor, _serverAddress, _accessToken, _userDataManager, _localization, _mediaSourceManager, _logger, _mediaEncoder)
|
var itemXml = new DidlBuilder(
|
||||||
.GetItemDidl(_config.GetDlnaConfiguration(), item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
profile,
|
||||||
|
user,
|
||||||
|
_imageProcessor,
|
||||||
|
_serverAddress,
|
||||||
|
_accessToken,
|
||||||
|
_userDataManager,
|
||||||
|
_localization,
|
||||||
|
_mediaSourceManager,
|
||||||
|
_logger,
|
||||||
|
_mediaEncoder,
|
||||||
|
_libraryManager)
|
||||||
|
.GetItemDidl(item, user, null, _session.DeviceId, new Filter(), playlistItem.StreamInfo);
|
||||||
|
|
||||||
playlistItem.Didl = itemXml;
|
playlistItem.Didl = itemXml;
|
||||||
|
|
||||||
|
@ -570,30 +579,31 @@ namespace Emby.Dlna.PlayTo
|
||||||
/// Plays the items.
|
/// Plays the items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="items">The items.</param>
|
/// <param name="items">The items.</param>
|
||||||
/// <returns></returns>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items)
|
/// <returns><c>true</c> on success.</returns>
|
||||||
|
private async Task<bool> PlayItems(IEnumerable<PlaylistItem> items, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Playlist.Clear();
|
_playlist.Clear();
|
||||||
Playlist.AddRange(items);
|
_playlist.AddRange(items);
|
||||||
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, Playlist.Count);
|
_logger.LogDebug("{0} - Playing {1} items", _session.DeviceName, _playlist.Count);
|
||||||
|
|
||||||
await SetPlaylistIndex(0).ConfigureAwait(false);
|
await SetPlaylistIndex(0, cancellationToken).ConfigureAwait(false);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SetPlaylistIndex(int index)
|
private async Task SetPlaylistIndex(int index, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (index < 0 || index >= Playlist.Count)
|
if (index < 0 || index >= _playlist.Count)
|
||||||
{
|
{
|
||||||
Playlist.Clear();
|
_playlist.Clear();
|
||||||
await _device.SetStop(CancellationToken.None);
|
await _device.SetStop(cancellationToken).ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentPlaylistIndex = index;
|
_currentPlaylistIndex = index;
|
||||||
var currentitem = Playlist[index];
|
var currentitem = _playlist[index];
|
||||||
|
|
||||||
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, CancellationToken.None);
|
await _device.SetAvTransport(currentitem.StreamUrl, GetDlnaHeaders(currentitem), currentitem.Didl, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var streamInfo = currentitem.StreamInfo;
|
var streamInfo = currentitem.StreamInfo;
|
||||||
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
if (streamInfo.StartPositionTicks > 0 && EnableClientSideSeek(streamInfo))
|
||||||
|
@ -602,10 +612,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <inheritdoc />
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
@ -624,19 +631,17 @@ namespace Emby.Dlna.PlayTo
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.PlaybackStart -= _device_PlaybackStart;
|
_device.PlaybackStart -= OnDevicePlaybackStart;
|
||||||
_device.PlaybackProgress -= _device_PlaybackProgress;
|
_device.PlaybackProgress -= OnDevicePlaybackProgress;
|
||||||
_device.PlaybackStopped -= _device_PlaybackStopped;
|
_device.PlaybackStopped -= OnDevicePlaybackStopped;
|
||||||
_device.MediaChanged -= _device_MediaChanged;
|
_device.MediaChanged -= OnDeviceMediaChanged;
|
||||||
_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
|
_deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
|
||||||
_device.OnDeviceUnavailable = null;
|
_device.OnDeviceUnavailable = null;
|
||||||
_device = null;
|
_device = null;
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
|
||||||
|
|
||||||
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
private Task SendGeneralCommand(GeneralCommand command, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
|
if (Enum.TryParse(command.Name, true, out GeneralCommandType commandType))
|
||||||
|
@ -713,7 +718,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (info.Item != null)
|
if (info.Item != null)
|
||||||
{
|
{
|
||||||
var newPosition = GetProgressPositionTicks(media, info) ?? 0;
|
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
||||||
|
|
||||||
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
||||||
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, newIndex, info.SubtitleStreamIndex);
|
||||||
|
@ -738,7 +743,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (info.Item != null)
|
if (info.Item != null)
|
||||||
{
|
{
|
||||||
var newPosition = GetProgressPositionTicks(media, info) ?? 0;
|
var newPosition = GetProgressPositionTicks(info) ?? 0;
|
||||||
|
|
||||||
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
var user = !_session.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(_session.UserId) : null;
|
||||||
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
|
var newItem = CreatePlaylistItem(info.Item, user, newPosition, info.MediaSourceId, info.AudioStreamIndex, newIndex);
|
||||||
|
@ -852,8 +857,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = url.IndexOf('?');
|
var index = url.IndexOf('?', StringComparison.Ordinal);
|
||||||
if (index == -1) return request;
|
if (index == -1)
|
||||||
|
{
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
var query = url.Substring(index + 1);
|
var query = url.Substring(index + 1);
|
||||||
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
Dictionary<string, string> values = QueryHelpers.ParseQuery(query).ToDictionary(kv => kv.Key, kv => kv.Value.ToString());
|
||||||
|
@ -900,7 +908,8 @@ namespace Emby.Dlna.PlayTo
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SendMessage<T>(string name, string messageId, T data, ISessionController[] allControllers, CancellationToken cancellationToken)
|
/// <inheritdoc />
|
||||||
|
public Task SendMessage<T>(string name, Guid messageId, T data, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
|
@ -916,10 +925,12 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
return SendPlayCommand(data as PlayRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "PlayState", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
return SendPlaystateCommand(data as PlaystateRequest, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(name, "GeneralCommand", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
return SendGeneralCommand(data as GeneralCommand, cancellationToken);
|
||||||
|
|
|
@ -23,7 +23,7 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
public class PlayToManager : IDisposable
|
public sealed class PlayToManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
|
@ -231,6 +231,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
|
||||||
|
@ -244,6 +245,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sessionLock.Dispose();
|
||||||
|
_disposeCancellationTokenSource.Dispose();
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,18 +32,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
DeviceService service,
|
DeviceService service,
|
||||||
string command,
|
string command,
|
||||||
string postData,
|
string postData,
|
||||||
bool logRequest = true,
|
string header = null,
|
||||||
string header = null)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var cancellationToken = CancellationToken.None;
|
|
||||||
|
|
||||||
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
var url = NormalizeServiceUrl(baseUrl, service.ControlUrl);
|
||||||
using (var response = await PostSoapDataAsync(
|
using (var response = await PostSoapDataAsync(
|
||||||
url,
|
url,
|
||||||
$"\"{service.ServiceType}#{command}\"",
|
$"\"{service.ServiceType}#{command}\"",
|
||||||
postData,
|
postData,
|
||||||
header,
|
header,
|
||||||
logRequest,
|
|
||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false))
|
.ConfigureAwait(false))
|
||||||
using (var stream = response.Content)
|
using (var stream = response.Content)
|
||||||
|
@ -63,7 +60,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
return serviceUrl;
|
return serviceUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!serviceUrl.StartsWith("/"))
|
if (!serviceUrl.StartsWith("/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
serviceUrl = "/" + serviceUrl;
|
serviceUrl = "/" + serviceUrl;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +124,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
string soapAction,
|
string soapAction,
|
||||||
string postData,
|
string postData,
|
||||||
string header,
|
string header,
|
||||||
bool logRequest,
|
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (soapAction[0] != '\"')
|
if (soapAction[0] != '\"')
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Emby.Dlna.Profiles
|
||||||
{
|
{
|
||||||
Name = "Generic Device";
|
Name = "Generic Device";
|
||||||
|
|
||||||
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
|
ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
|
||||||
|
|
||||||
Manufacturer = "Jellyfin";
|
Manufacturer = "Jellyfin";
|
||||||
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
|
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>true</RequiresPlainFolders>
|
<RequiresPlainFolders>true</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>10</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>true</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>true</RequiresPlainFolders>
|
<RequiresPlainFolders>true</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<SonyAggregationFlags>10</SonyAggregationFlags>
|
<SonyAggregationFlags>10</SonyAggregationFlags>
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>5</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>5</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>40</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>40</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
<MaxStaticBitrate>140000000</MaxStaticBitrate>
|
||||||
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
<MusicStreamingTranscodingBitrate>192000</MusicStreamingTranscodingBitrate>
|
||||||
<MaxStaticMusicBitrate xsi:nil="true" />
|
<MaxStaticMusicBitrate xsi:nil="true" />
|
||||||
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*</ProtocolInfo>
|
<ProtocolInfo>http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*</ProtocolInfo>
|
||||||
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
<TimelineOffsetSeconds>0</TimelineOffsetSeconds>
|
||||||
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
<RequiresPlainVideoItems>false</RequiresPlainVideoItems>
|
||||||
<RequiresPlainFolders>false</RequiresPlainFolders>
|
<RequiresPlainFolders>false</RequiresPlainFolders>
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{08FFF49B-F175-4807-A2B5-73B0EBD9F716}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -8,7 +8,6 @@ using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
@ -33,8 +32,7 @@ namespace Emby.Drawing
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly IImageEncoder _imageEncoder;
|
private readonly IImageEncoder _imageEncoder;
|
||||||
private readonly Func<ILibraryManager> _libraryManager;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly Func<IMediaEncoder> _mediaEncoder;
|
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
@ -45,20 +43,17 @@ namespace Emby.Drawing
|
||||||
/// <param name="appPaths">The server application paths.</param>
|
/// <param name="appPaths">The server application paths.</param>
|
||||||
/// <param name="fileSystem">The filesystem.</param>
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
/// <param name="imageEncoder">The image encoder.</param>
|
/// <param name="imageEncoder">The image encoder.</param>
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
|
||||||
/// <param name="mediaEncoder">The media encoder.</param>
|
/// <param name="mediaEncoder">The media encoder.</param>
|
||||||
public ImageProcessor(
|
public ImageProcessor(
|
||||||
ILogger<ImageProcessor> logger,
|
ILogger<ImageProcessor> logger,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IImageEncoder imageEncoder,
|
IImageEncoder imageEncoder,
|
||||||
Func<ILibraryManager> libraryManager,
|
IMediaEncoder mediaEncoder)
|
||||||
Func<IMediaEncoder> mediaEncoder)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_imageEncoder = imageEncoder;
|
_imageEncoder = imageEncoder;
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
}
|
}
|
||||||
|
@ -121,26 +116,9 @@ namespace Emby.Drawing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
public async Task<(string path, string mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
|
||||||
{
|
{
|
||||||
if (options == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(options));
|
|
||||||
}
|
|
||||||
|
|
||||||
var libraryManager = _libraryManager();
|
|
||||||
|
|
||||||
ItemImageInfo originalImage = options.Image;
|
ItemImageInfo originalImage = options.Image;
|
||||||
BaseItem item = options.Item;
|
BaseItem item = options.Item;
|
||||||
|
|
||||||
if (!originalImage.IsLocalFile)
|
|
||||||
{
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
item = libraryManager.GetItemById(options.ItemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
originalImage = await libraryManager.ConvertImageToLocal(item, originalImage, options.ImageIndex).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
string originalImagePath = originalImage.Path;
|
string originalImagePath = originalImage.Path;
|
||||||
DateTime dateModified = originalImage.DateModified;
|
DateTime dateModified = originalImage.DateModified;
|
||||||
ImageDimensions? originalImageSize = null;
|
ImageDimensions? originalImageSize = null;
|
||||||
|
@ -312,10 +290,6 @@ namespace Emby.Drawing
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info)
|
||||||
=> GetImageDimensions(item, info, true);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ImageDimensions GetImageDimensions(BaseItem item, ItemImageInfo info, bool updateItem)
|
|
||||||
{
|
{
|
||||||
int width = info.Width;
|
int width = info.Width;
|
||||||
int height = info.Height;
|
int height = info.Height;
|
||||||
|
@ -332,11 +306,6 @@ namespace Emby.Drawing
|
||||||
info.Width = size.Width;
|
info.Width = size.Width;
|
||||||
info.Height = size.Height;
|
info.Height = size.Height;
|
||||||
|
|
||||||
if (updateItem)
|
|
||||||
{
|
|
||||||
_libraryManager().UpdateImages(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,19 +320,12 @@ namespace Emby.Drawing
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
public string GetImageCacheTag(BaseItem item, ChapterInfo chapter)
|
||||||
{
|
{
|
||||||
try
|
return GetImageCacheTag(item, new ItemImageInfo
|
||||||
{
|
{
|
||||||
return GetImageCacheTag(item, new ItemImageInfo
|
Path = chapter.ImagePath,
|
||||||
{
|
Type = ImageType.Chapter,
|
||||||
Path = chapter.ImagePath,
|
DateModified = chapter.ImageDateModified
|
||||||
Type = ImageType.Chapter,
|
});
|
||||||
DateModified = chapter.ImageDateModified
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
|
||||||
|
@ -384,13 +346,13 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
string filename = (originalImagePath + dateModified.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
string cacheExtension = _mediaEncoder().SupportsEncoder("libwebp") ? ".webp" : ".png";
|
string cacheExtension = _mediaEncoder.SupportsEncoder("libwebp") ? ".webp" : ".png";
|
||||||
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
var outputPath = Path.Combine(_appPaths.ImageCachePath, "converted-images", filename + cacheExtension);
|
||||||
|
|
||||||
var file = _fileSystem.GetFileInfo(outputPath);
|
var file = _fileSystem.GetFileInfo(outputPath);
|
||||||
if (!file.Exists)
|
if (!file.Exists)
|
||||||
{
|
{
|
||||||
await _mediaEncoder().ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
await _mediaEncoder.ConvertImage(originalImagePath, outputPath).ConfigureAwait(false);
|
||||||
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
dateModified = _fileSystem.GetLastWriteTimeUtc(outputPath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
|
||||||
|
@ -21,8 +21,7 @@ namespace Emby.Naming.Audio
|
||||||
public bool IsMultiPart(string path)
|
public bool IsMultiPart(string path)
|
||||||
{
|
{
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
if (filename.Length == 0)
|
||||||
if (string.IsNullOrEmpty(filename))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -39,18 +38,22 @@ namespace Emby.Naming.Audio
|
||||||
filename = filename.Replace(')', ' ');
|
filename = filename.Replace(')', ' ');
|
||||||
filename = Regex.Replace(filename, @"\s+", " ");
|
filename = Regex.Replace(filename, @"\s+", " ");
|
||||||
|
|
||||||
filename = filename.TrimStart();
|
ReadOnlySpan<char> trimmedFilename = filename.TrimStart();
|
||||||
|
|
||||||
foreach (var prefix in _options.AlbumStackingPrefixes)
|
foreach (var prefix in _options.AlbumStackingPrefixes)
|
||||||
{
|
{
|
||||||
if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
|
if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = filename.Substring(prefix.Length);
|
var tmp = trimmedFilename.Slice(prefix.Length).Trim();
|
||||||
|
|
||||||
tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
|
int index = tmp.IndexOf(' ');
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
tmp = tmp.Slice(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -11,7 +12,7 @@ namespace Emby.Naming.Audio
|
||||||
{
|
{
|
||||||
public static bool IsAudioFile(string path, NamingOptions options)
|
public static bool IsAudioFile(string path, NamingOptions options)
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(path) ?? string.Empty;
|
var extension = Path.GetExtension(path);
|
||||||
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ namespace Emby.Naming.AudioBook
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc />
|
||||||
public int CompareTo(AudioBookFileInfo other)
|
public int CompareTo(AudioBookFileInfo other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(this, other))
|
if (ReferenceEquals(this, other))
|
||||||
|
|
|
@ -29,11 +29,7 @@ namespace Emby.Naming.AudioBook
|
||||||
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
// Filter out all extras, otherwise they could cause stacks to not be resolved
|
||||||
// See the unit test TestStackedWithTrailer
|
// See the unit test TestStackedWithTrailer
|
||||||
var metadata = audiobookFileInfos
|
var metadata = audiobookFileInfos
|
||||||
.Select(i => new FileSystemMetadata
|
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||||
{
|
|
||||||
FullName = i.Path,
|
|
||||||
IsDirectory = i.IsDirectory
|
|
||||||
});
|
|
||||||
|
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = new StackResolver(_options)
|
||||||
.ResolveAudioBooks(metadata);
|
.ResolveAudioBooks(metadata);
|
||||||
|
@ -42,11 +38,7 @@ namespace Emby.Naming.AudioBook
|
||||||
{
|
{
|
||||||
var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList();
|
var stackFiles = stack.Files.Select(i => audioBookResolver.Resolve(i, stack.IsDirectoryStack)).ToList();
|
||||||
stackFiles.Sort();
|
stackFiles.Sort();
|
||||||
var info = new AudioBookInfo
|
var info = new AudioBookInfo { Files = stackFiles, Name = stack.Name };
|
||||||
{
|
|
||||||
Files = stackFiles,
|
|
||||||
Name = stack.Name
|
|
||||||
};
|
|
||||||
|
|
||||||
yield return info;
|
yield return info;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,6 @@ namespace Emby.Naming.Common
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeExpression()
|
|
||||||
: this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Expression
|
public string Expression
|
||||||
{
|
{
|
||||||
get => _expression;
|
get => _expression;
|
||||||
|
@ -48,6 +43,6 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
public string[] DateTimeFormats { get; set; }
|
public string[] DateTimeFormats { get; set; }
|
||||||
|
|
||||||
public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,8 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
|
||||||
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
|
@ -505,7 +506,63 @@ namespace Emby.Naming.Common
|
||||||
RuleType = ExtraRuleType.Suffix,
|
RuleType = ExtraRuleType.Suffix,
|
||||||
Token = "-short",
|
Token = "-short",
|
||||||
MediaType = MediaType.Video
|
MediaType = MediaType.Video
|
||||||
}
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.BehindTheScenes,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "behind the scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.DeletedScene,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "deleted scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Interview,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "interviews",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Scene,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "scenes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Sample,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "samples",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Clip,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "shorts",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Clip,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "featurettes",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
|
new ExtraRule
|
||||||
|
{
|
||||||
|
ExtraType = ExtraType.Unknown,
|
||||||
|
RuleType = ExtraRuleType.DirectoryName,
|
||||||
|
Token = "extras",
|
||||||
|
MediaType = MediaType.Video,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Format3DRules = new[]
|
Format3DRules = new[]
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{E5AF7B26-2239-4CE0-B477-0AA2018EDAA2}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#nullable enable
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubtitleInfo ParseFile(string path)
|
public SubtitleInfo? ParseFile(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (path.Length == 0)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentException("File path can't be empty.", nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
@ -37,7 +38,8 @@ namespace Emby.Naming.Subtitles
|
||||||
IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
IsForced = _options.SubtitleForcedFlags.Any(i => flags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
||||||
};
|
};
|
||||||
|
|
||||||
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase) && !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
var parts = flags.Where(i => !_options.SubtitleDefaultFlags.Contains(i, StringComparer.OrdinalIgnoreCase)
|
||||||
|
&& !_options.SubtitleForcedFlags.Contains(i, StringComparer.OrdinalIgnoreCase))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Should have a name, language and file extension
|
// Should have a name, language and file extension
|
||||||
|
@ -51,11 +53,6 @@ namespace Emby.Naming.Subtitles
|
||||||
|
|
||||||
private string[] GetFlags(string path)
|
private string[] GetFlags(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
|
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
|
||||||
|
|
||||||
var file = Path.GetFileName(path);
|
var file = Path.GetFileName(path);
|
||||||
|
|
|
@ -18,7 +18,13 @@ namespace Emby.Naming.TV
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodePathParserResult Parse(string path, bool isDirectory, bool? isNamed = null, bool? isOptimistic = null, bool? supportsAbsoluteNumbers = null, bool fillExtendedInfo = true)
|
public EpisodePathParserResult Parse(
|
||||||
|
string path,
|
||||||
|
bool isDirectory,
|
||||||
|
bool? isNamed = null,
|
||||||
|
bool? isOptimistic = null,
|
||||||
|
bool? supportsAbsoluteNumbers = null,
|
||||||
|
bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
// Added to be able to use regex patterns which require a file extension.
|
// Added to be able to use regex patterns which require a file extension.
|
||||||
// There were no failed tests without this block, but to be safe, we can keep it until
|
// There were no failed tests without this block, but to be safe, we can keep it until
|
||||||
|
@ -64,7 +70,7 @@ namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
result.SeriesName = result.SeriesName
|
result.SeriesName = result.SeriesName
|
||||||
.Trim()
|
.Trim()
|
||||||
.Trim(new[] { '_', '.', '-' })
|
.Trim('_', '.', '-')
|
||||||
.Trim();
|
.Trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace Emby.Naming.TV
|
||||||
public int? SeasonNumber { get; set; }
|
public int? SeasonNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult"/> is success.
|
/// Gets or sets a value indicating whether this <see cref="SeasonPathParserResult" /> is success.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if success; otherwise, <c>false</c>.</value>
|
||||||
public bool Success { get; set; }
|
public bool Success { get; set; }
|
||||||
|
|
|
@ -80,6 +80,15 @@ namespace Emby.Naming.Video
|
||||||
result.Rule = rule;
|
result.Rule = rule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||||
|
{
|
||||||
|
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
||||||
|
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.ExtraType = rule.ExtraType;
|
||||||
|
result.Rule = rule;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,30 +5,29 @@ using MediaType = Emby.Naming.Common.MediaType;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
|
||||||
|
/// </summary>
|
||||||
public class ExtraRule
|
public class ExtraRule
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the token.
|
/// Gets or sets the token to use for matching against the file path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The token.</value>
|
|
||||||
public string Token { get; set; }
|
public string Token { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the extra.
|
/// Gets or sets the type of the extra to return when matched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the extra.</value>
|
|
||||||
public ExtraType ExtraType { get; set; }
|
public ExtraType ExtraType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the rule.
|
/// Gets or sets the type of the rule.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the rule.</value>
|
|
||||||
public ExtraRuleType RuleType { get; set; }
|
public ExtraRuleType RuleType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type of the media.
|
/// Gets or sets the type of the media to return when matched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type of the media.</value>
|
|
||||||
public MediaType MediaType { get; set; }
|
public MediaType MediaType { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,23 @@ namespace Emby.Naming.Video
|
||||||
public enum ExtraRuleType
|
public enum ExtraRuleType
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The suffix
|
/// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Suffix = 0,
|
Suffix = 0,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The filename
|
/// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Filename = 1,
|
Filename = 1,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The regex
|
/// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Regex = 2
|
Regex = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
|
||||||
|
/// </summary>
|
||||||
|
DirectoryName = 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,31 +21,24 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
public IEnumerable<FileStack> ResolveDirectories(IEnumerable<string> files)
|
||||||
{
|
{
|
||||||
return Resolve(files.Select(i => new FileSystemMetadata
|
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = true }));
|
||||||
{
|
|
||||||
FullName = i,
|
|
||||||
IsDirectory = true
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
public IEnumerable<FileStack> ResolveFiles(IEnumerable<string> files)
|
||||||
{
|
{
|
||||||
return Resolve(files.Select(i => new FileSystemMetadata
|
return Resolve(files.Select(i => new FileSystemMetadata { FullName = i, IsDirectory = false }));
|
||||||
{
|
|
||||||
FullName = i,
|
|
||||||
IsDirectory = false
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
var groupedDirectoryFiles = files.GroupBy(file =>
|
||||||
|
file.IsDirectory
|
||||||
|
? file.FullName
|
||||||
|
: Path.GetDirectoryName(file.FullName));
|
||||||
|
|
||||||
|
foreach (var directory in groupedDirectoryFiles)
|
||||||
{
|
{
|
||||||
var stack = new FileStack()
|
var stack = new FileStack { Name = Path.GetFileName(directory.Key), IsDirectoryStack = false };
|
||||||
{
|
|
||||||
Name = Path.GetFileName(directory.Key),
|
|
||||||
IsDirectoryStack = false
|
|
||||||
};
|
|
||||||
foreach (var file in directory)
|
foreach (var file in directory)
|
||||||
{
|
{
|
||||||
if (file.IsDirectory)
|
if (file.IsDirectory)
|
||||||
|
|
|
@ -77,7 +77,9 @@ namespace Emby.Naming.Video
|
||||||
/// Gets the file name without extension.
|
/// Gets the file name without extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The file name without extension.</value>
|
/// <value>The file name without extension.</value>
|
||||||
public string FileNameWithoutExtension => !IsDirectory ? System.IO.Path.GetFileNameWithoutExtension(Path) : System.IO.Path.GetFileName(Path);
|
public string FileNameWithoutExtension => !IsDirectory
|
||||||
|
? System.IO.Path.GetFileNameWithoutExtension(Path)
|
||||||
|
: System.IO.Path.GetFileName(Path);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
|
|
|
@ -33,11 +33,7 @@ namespace Emby.Naming.Video
|
||||||
// See the unit test TestStackedWithTrailer
|
// See the unit test TestStackedWithTrailer
|
||||||
var nonExtras = videoInfos
|
var nonExtras = videoInfos
|
||||||
.Where(i => i.ExtraType == null)
|
.Where(i => i.ExtraType == null)
|
||||||
.Select(i => new FileSystemMetadata
|
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
|
||||||
{
|
|
||||||
FullName = i.Path,
|
|
||||||
IsDirectory = i.IsDirectory
|
|
||||||
});
|
|
||||||
|
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = new StackResolver(_options)
|
||||||
.Resolve(nonExtras).ToList();
|
.Resolve(nonExtras).ToList();
|
||||||
|
@ -57,11 +53,7 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
var extraBaseNames = new List<string>
|
var extraBaseNames = new List<string> { stack.Name, Path.GetFileNameWithoutExtension(stack.Files[0]) };
|
||||||
{
|
|
||||||
stack.Name,
|
|
||||||
Path.GetFileNameWithoutExtension(stack.Files[0])
|
|
||||||
};
|
|
||||||
|
|
||||||
var extras = GetExtras(remainingFiles, extraBaseNames);
|
var extras = GetExtras(remainingFiles, extraBaseNames);
|
||||||
|
|
||||||
|
@ -83,10 +75,7 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
foreach (var media in standaloneMedia)
|
foreach (var media in standaloneMedia)
|
||||||
{
|
{
|
||||||
var info = new VideoInfo(media.Name)
|
var info = new VideoInfo(media.Name) { Files = new List<VideoFileInfo> { media } };
|
||||||
{
|
|
||||||
Files = new List<VideoFileInfo> { media }
|
|
||||||
};
|
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
|
||||||
|
@ -222,8 +211,8 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
testFilename = testFilename.Substring(folderName.Length).Trim();
|
testFilename = testFilename.Substring(folderName.Length).Trim();
|
||||||
return string.IsNullOrEmpty(testFilename)
|
return string.IsNullOrEmpty(testFilename)
|
||||||
|| testFilename[0] == '-'
|
|| testFilename[0] == '-'
|
||||||
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
|
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -238,8 +227,9 @@ namespace Emby.Naming.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
return remainingFiles
|
return remainingFiles
|
||||||
.Where(i => i.ExtraType == null)
|
.Where(i => i.ExtraType != null)
|
||||||
.Where(i => baseNames.Any(b => i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
|
.Where(i => baseNames.Any(b =>
|
||||||
|
i.FileNameWithoutExtension.StartsWith(b, StringComparison.OrdinalIgnoreCase)))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,14 +89,14 @@ namespace Emby.Naming.Video
|
||||||
if (parseName)
|
if (parseName)
|
||||||
{
|
{
|
||||||
var cleanDateTimeResult = CleanDateTime(name);
|
var cleanDateTimeResult = CleanDateTime(name);
|
||||||
|
name = cleanDateTimeResult.Name;
|
||||||
|
year = cleanDateTimeResult.Year;
|
||||||
|
|
||||||
if (extraResult.ExtraType == null
|
if (extraResult.ExtraType == null
|
||||||
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
&& TryCleanString(name, out ReadOnlySpan<char> newName))
|
||||||
{
|
{
|
||||||
name = newName.ToString();
|
name = newName.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
year = cleanDateTimeResult.Year;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new VideoFileInfo
|
return new VideoFileInfo
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1402
|
#pragma warning disable SA1402
|
||||||
#pragma warning disable SA1600
|
|
||||||
#pragma warning disable SA1649
|
#pragma warning disable SA1649
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -135,19 +134,19 @@ namespace Emby.Notifications.Api
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetNotificationTypes request)
|
public object Get(GetNotificationTypes request)
|
||||||
{
|
{
|
||||||
return _notificationManager.GetNotificationTypes();
|
return _notificationManager.GetNotificationTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetNotificationServices request)
|
public object Get(GetNotificationServices request)
|
||||||
{
|
{
|
||||||
return _notificationManager.GetNotificationServices().ToList();
|
return _notificationManager.GetNotificationServices().ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetNotificationsSummary request)
|
public object Get(GetNotificationsSummary request)
|
||||||
{
|
{
|
||||||
return new NotificationsSummary
|
return new NotificationsSummary
|
||||||
|
@ -171,17 +170,17 @@ namespace Emby.Notifications.Api
|
||||||
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
return _notificationManager.SendNotification(notification, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public void Post(MarkRead request)
|
public void Post(MarkRead request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public void Post(MarkUnread request)
|
public void Post(MarkUnread request)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request")]
|
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "request", Justification = "Required for ServiceStack")]
|
||||||
public object Get(GetNotifications request)
|
public object Get(GetNotifications request)
|
||||||
{
|
{
|
||||||
return new NotificationResult();
|
return new NotificationResult();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{2E030C33-6923-4530-9E54-FA29FA6AD1A9}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
|
|
@ -143,7 +143,7 @@ namespace Emby.Notifications
|
||||||
|
|
||||||
var notification = new NotificationRequest
|
var notification = new NotificationRequest
|
||||||
{
|
{
|
||||||
Description = "Please see jellyfin.media for details.",
|
Description = "Please see jellyfin.org for details.",
|
||||||
NotificationType = type,
|
NotificationType = type,
|
||||||
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
|
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<ProjectGuid>{89AB4548-770D-41FD-A891-8DAFF44F452C}</ProjectGuid>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
|
||||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
|
||||||
|
|
|
@ -160,7 +160,7 @@ namespace Emby.Photos
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var size = _imageProcessor.GetImageDimensions(item, img, false);
|
var size = _imageProcessor.GetImageDimensions(item, img);
|
||||||
|
|
||||||
if (size.Width > 0 && size.Height > 0)
|
if (size.Width > 0 && size.Height > 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Common.Updates;
|
using MediaBrowser.Common.Updates;
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Devices;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Plugins;
|
using MediaBrowser.Controller.Plugins;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
|
@ -28,9 +24,12 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Entry point for the activity logger.
|
||||||
|
/// </summary>
|
||||||
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
public sealed class ActivityLogEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger<ActivityLogEntryPoint> _logger;
|
||||||
private readonly IInstallationManager _installationManager;
|
private readonly IInstallationManager _installationManager;
|
||||||
private readonly ISessionManager _sessionManager;
|
private readonly ISessionManager _sessionManager;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
|
@ -38,25 +37,21 @@ namespace Emby.Server.Implementations.Activity
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly ISubtitleManager _subManager;
|
private readonly ISubtitleManager _subManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IDeviceManager _deviceManager;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger"></param>
|
/// <param name="logger">The logger.</param>
|
||||||
/// <param name="sessionManager"></param>
|
/// <param name="sessionManager">The session manager.</param>
|
||||||
/// <param name="deviceManager"></param>
|
/// <param name="taskManager">The task manager.</param>
|
||||||
/// <param name="taskManager"></param>
|
/// <param name="activityManager">The activity manager.</param>
|
||||||
/// <param name="activityManager"></param>
|
/// <param name="localization">The localization manager.</param>
|
||||||
/// <param name="localization"></param>
|
/// <param name="installationManager">The installation manager.</param>
|
||||||
/// <param name="installationManager"></param>
|
/// <param name="subManager">The subtitle manager.</param>
|
||||||
/// <param name="subManager"></param>
|
/// <param name="userManager">The user manager.</param>
|
||||||
/// <param name="userManager"></param>
|
|
||||||
/// <param name="appHost"></param>
|
|
||||||
public ActivityLogEntryPoint(
|
public ActivityLogEntryPoint(
|
||||||
ILogger<ActivityLogEntryPoint> logger,
|
ILogger<ActivityLogEntryPoint> logger,
|
||||||
ISessionManager sessionManager,
|
ISessionManager sessionManager,
|
||||||
IDeviceManager deviceManager,
|
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
IActivityManager activityManager,
|
IActivityManager activityManager,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
|
@ -66,7 +61,6 @@ namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_sessionManager = sessionManager;
|
_sessionManager = sessionManager;
|
||||||
_deviceManager = deviceManager;
|
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_activityManager = activityManager;
|
_activityManager = activityManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
|
@ -75,6 +69,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_taskManager.TaskCompleted += OnTaskCompleted;
|
_taskManager.TaskCompleted += OnTaskCompleted;
|
||||||
|
@ -99,52 +94,38 @@ namespace Emby.Server.Implementations.Activity
|
||||||
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
_userManager.UserPolicyUpdated += OnUserPolicyUpdated;
|
||||||
_userManager.UserLockedOut += OnUserLockedOut;
|
_userManager.UserLockedOut += OnUserLockedOut;
|
||||||
|
|
||||||
_deviceManager.CameraImageUploaded += OnCameraImageUploaded;
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
|
private async void OnUserLockedOut(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
CultureInfo.InvariantCulture,
|
||||||
CultureInfo.InvariantCulture,
|
_localization.GetLocalizedString("UserLockedOutWithName"),
|
||||||
_localization.GetLocalizedString("CameraImageUploadedFrom"),
|
e.Argument.Name),
|
||||||
e.Argument.Device.Name),
|
NotificationType.UserLockedOut.ToString(),
|
||||||
Type = NotificationType.CameraImageUploaded.ToString()
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
|
private async void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("UserLockedOutWithName"),
|
|
||||||
e.Argument.Name),
|
|
||||||
Type = NotificationType.UserLockedOut.ToString(),
|
|
||||||
UserId = e.Argument.Id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
|
|
||||||
{
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
|
||||||
e.Provider,
|
e.Provider,
|
||||||
Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
Notifications.NotificationEntryPoint.GetItemName(e.Item)),
|
||||||
Type = "SubtitleDownloadFailure",
|
"SubtitleDownloadFailure",
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
ShortOverview = e.Exception.Message
|
ShortOverview = e.Exception.Message
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
private async void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
|
@ -167,15 +148,19 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
var user = e.Users[0];
|
var user = e.Users[0];
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
|
CultureInfo.InvariantCulture,
|
||||||
Type = GetPlaybackStoppedNotificationType(item.MediaType),
|
_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
|
||||||
UserId = user.Id
|
user.Name,
|
||||||
});
|
GetItemName(item),
|
||||||
|
e.DeviceName),
|
||||||
|
GetPlaybackStoppedNotificationType(item.MediaType),
|
||||||
|
user.Id))
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
private async void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
|
||||||
{
|
{
|
||||||
var item = e.MediaInfo;
|
var item = e.MediaInfo;
|
||||||
|
|
||||||
|
@ -198,17 +183,16 @@ namespace Emby.Server.Implementations.Activity
|
||||||
|
|
||||||
var user = e.Users.First();
|
var user = e.Users.First();
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
_localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
|
||||||
user.Name,
|
user.Name,
|
||||||
GetItemName(item),
|
GetItemName(item),
|
||||||
e.DeviceName),
|
e.DeviceName),
|
||||||
Type = GetPlaybackNotificationType(item.MediaType),
|
GetPlaybackNotificationType(item.MediaType),
|
||||||
UserId = user.Id
|
user.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetItemName(BaseItemDto item)
|
private static string GetItemName(BaseItemDto item)
|
||||||
|
@ -258,236 +242,215 @@ namespace Emby.Server.Implementations.Activity
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionEnded(object sender, SessionEventArgs e)
|
private async void OnSessionEnded(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
if (string.IsNullOrEmpty(session.UserName))
|
||||||
{
|
{
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("DeviceOfflineWithName"),
|
|
||||||
session.DeviceName);
|
|
||||||
|
|
||||||
// Causing too much spam for now
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
await CreateLogEntry(new ActivityLog(
|
||||||
name = string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
_localization.GetLocalizedString("UserOfflineFromDevice"),
|
||||||
session.UserName,
|
session.UserName,
|
||||||
session.DeviceName);
|
session.DeviceName),
|
||||||
}
|
"SessionEnded",
|
||||||
|
session.UserId)
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
{
|
||||||
Name = name,
|
|
||||||
Type = "SessionEnded",
|
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
session.RemoteEndPoint),
|
session.RemoteEndPoint),
|
||||||
UserId = session.UserId
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
private async void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
|
||||||
{
|
{
|
||||||
var user = e.Argument.User;
|
var user = e.Argument.User;
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
|
_localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
|
||||||
user.Name),
|
user.Name),
|
||||||
Type = "AuthenticationSucceeded",
|
"AuthenticationSucceeded",
|
||||||
|
user.Id)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
e.Argument.SessionInfo.RemoteEndPoint),
|
e.Argument.SessionInfo.RemoteEndPoint),
|
||||||
UserId = user.Id
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
private async void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
|
_localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
|
||||||
e.Argument.Username),
|
e.Argument.Username),
|
||||||
Type = "AuthenticationFailed",
|
"AuthenticationFailed",
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
|
LogSeverity = LogLevel.Error,
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
e.Argument.RemoteEndPoint),
|
e.Argument.RemoteEndPoint),
|
||||||
Severity = LogLevel.Error
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
|
private async void OnUserPolicyUpdated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
_localization.GetLocalizedString("UserPolicyUpdatedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Name),
|
||||||
Type = "UserPolicyUpdated",
|
"UserPolicyUpdated",
|
||||||
UserId = e.Argument.Id
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserDeleted(object sender, GenericEventArgs<User> e)
|
private async void OnUserDeleted(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserDeletedWithName"),
|
_localization.GetLocalizedString("UserDeletedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Name),
|
||||||
Type = "UserDeleted"
|
"UserDeleted",
|
||||||
});
|
Guid.Empty))
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
|
private async void OnUserPasswordChanged(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
_localization.GetLocalizedString("UserPasswordChangedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Name),
|
||||||
Type = "UserPasswordChanged",
|
"UserPasswordChanged",
|
||||||
UserId = e.Argument.Id
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnUserCreated(object sender, GenericEventArgs<User> e)
|
private async void OnUserCreated(object sender, GenericEventArgs<MediaBrowser.Controller.Entities.User> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserCreatedWithName"),
|
_localization.GetLocalizedString("UserCreatedWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Name),
|
||||||
Type = "UserCreated",
|
"UserCreated",
|
||||||
UserId = e.Argument.Id
|
e.Argument.Id))
|
||||||
});
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSessionStarted(object sender, SessionEventArgs e)
|
private async void OnSessionStarted(object sender, SessionEventArgs e)
|
||||||
{
|
{
|
||||||
string name;
|
|
||||||
var session = e.SessionInfo;
|
var session = e.SessionInfo;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(session.UserName))
|
if (string.IsNullOrEmpty(session.UserName))
|
||||||
{
|
{
|
||||||
name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("DeviceOnlineWithName"),
|
|
||||||
session.DeviceName);
|
|
||||||
|
|
||||||
// Causing too much spam for now
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
await CreateLogEntry(new ActivityLog(
|
||||||
name = string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
_localization.GetLocalizedString("UserOnlineFromDevice"),
|
||||||
session.UserName,
|
session.UserName,
|
||||||
session.DeviceName);
|
session.DeviceName),
|
||||||
}
|
"SessionStarted",
|
||||||
|
session.UserId)
|
||||||
CreateLogEntry(new ActivityLogEntry
|
|
||||||
{
|
{
|
||||||
Name = name,
|
|
||||||
Type = "SessionStarted",
|
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("LabelIpAddressValue"),
|
_localization.GetLocalizedString("LabelIpAddressValue"),
|
||||||
session.RemoteEndPoint),
|
session.RemoteEndPoint)
|
||||||
UserId = session.UserId
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
|
private async void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, VersionInfo)> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
_localization.GetLocalizedString("PluginUpdatedWithName"),
|
||||||
e.Argument.Item1.Name),
|
e.Argument.Item1.Name),
|
||||||
Type = NotificationType.PluginUpdateInstalled.ToString(),
|
NotificationType.PluginUpdateInstalled.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.Item2.versionStr),
|
e.Argument.Item2.version),
|
||||||
Overview = e.Argument.Item2.description
|
Overview = e.Argument.Item2.changelog
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
private async void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
_localization.GetLocalizedString("PluginUninstalledWithName"),
|
||||||
e.Argument.Name),
|
e.Argument.Name),
|
||||||
Type = NotificationType.PluginUninstalled.ToString()
|
NotificationType.PluginUninstalled.ToString(),
|
||||||
});
|
Guid.Empty))
|
||||||
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
|
private async void OnPluginInstalled(object sender, GenericEventArgs<VersionInfo> e)
|
||||||
{
|
{
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("PluginInstalledWithName"),
|
_localization.GetLocalizedString("PluginInstalledWithName"),
|
||||||
e.Argument.name),
|
e.Argument.name),
|
||||||
Type = NotificationType.PluginInstalled.ToString(),
|
NotificationType.PluginInstalled.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
e.Argument.versionStr)
|
e.Argument.version)
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
private async void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
|
||||||
{
|
{
|
||||||
var installationInfo = e.InstallationInfo;
|
var installationInfo = e.InstallationInfo;
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
{
|
string.Format(
|
||||||
Name = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("NameInstallFailed"),
|
_localization.GetLocalizedString("NameInstallFailed"),
|
||||||
installationInfo.Name),
|
installationInfo.Name),
|
||||||
Type = NotificationType.InstallationFailed.ToString(),
|
NotificationType.InstallationFailed.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
|
{
|
||||||
ShortOverview = string.Format(
|
ShortOverview = string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
_localization.GetLocalizedString("VersionNumber"),
|
_localization.GetLocalizedString("VersionNumber"),
|
||||||
installationInfo.Version),
|
installationInfo.Version),
|
||||||
Overview = e.Exception.Message
|
Overview = e.Exception.Message
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
private async void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
|
||||||
{
|
{
|
||||||
var result = e.Result;
|
var result = e.Result;
|
||||||
var task = e.Task;
|
var task = e.Task;
|
||||||
|
|
||||||
var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
|
if (task.ScheduledTask is IConfigurableScheduledTask activityTask
|
||||||
if (activityTask != null && !activityTask.IsLogged)
|
&& !activityTask.IsLogged)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -512,22 +475,20 @@ namespace Emby.Server.Implementations.Activity
|
||||||
vals.Add(e.Result.LongErrorMessage);
|
vals.Add(e.Result.LongErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateLogEntry(new ActivityLogEntry
|
await CreateLogEntry(new ActivityLog(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("ScheduledTaskFailedWithName"), task.Name),
|
||||||
|
NotificationType.TaskFailed.ToString(),
|
||||||
|
Guid.Empty)
|
||||||
{
|
{
|
||||||
Name = string.Format(
|
LogSeverity = LogLevel.Error,
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
_localization.GetLocalizedString("ScheduledTaskFailedWithName"),
|
|
||||||
task.Name),
|
|
||||||
Type = NotificationType.TaskFailed.ToString(),
|
|
||||||
Overview = string.Join(Environment.NewLine, vals),
|
Overview = string.Join(Environment.NewLine, vals),
|
||||||
ShortOverview = runningTime,
|
ShortOverview = runningTime
|
||||||
Severity = LogLevel.Error
|
}).ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateLogEntry(ActivityLogEntry entry)
|
private async Task CreateLogEntry(ActivityLog entry)
|
||||||
=> _activityManager.Create(entry);
|
=> await _activityManager.CreateAsync(entry).ConfigureAwait(false);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@ -554,14 +515,12 @@ namespace Emby.Server.Implementations.Activity
|
||||||
_userManager.UserDeleted -= OnUserDeleted;
|
_userManager.UserDeleted -= OnUserDeleted;
|
||||||
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
_userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
|
||||||
_userManager.UserLockedOut -= OnUserLockedOut;
|
_userManager.UserLockedOut -= OnUserLockedOut;
|
||||||
|
|
||||||
_deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Constructs a user-friendly string for this TimeSpan instance.
|
/// Constructs a user-friendly string for this TimeSpan instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string ToUserFriendlyString(TimeSpan span)
|
private static string ToUserFriendlyString(TimeSpan span)
|
||||||
{
|
{
|
||||||
const int DaysInYear = 365;
|
const int DaysInYear = 365;
|
||||||
const int DaysInMonth = 30;
|
const int DaysInMonth = 30;
|
||||||
|
@ -575,7 +534,7 @@ namespace Emby.Server.Implementations.Activity
|
||||||
{
|
{
|
||||||
int years = days / DaysInYear;
|
int years = days / DaysInYear;
|
||||||
values.Add(CreateValueString(years, "year"));
|
values.Add(CreateValueString(years, "year"));
|
||||||
days = days % DaysInYear;
|
days %= DaysInYear;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Number of months
|
// Number of months
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Model.Activity;
|
|
||||||
using MediaBrowser.Model.Events;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
|
||||||
{
|
|
||||||
public class ActivityManager : IActivityManager
|
|
||||||
{
|
|
||||||
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
|
|
||||||
|
|
||||||
private readonly IActivityRepository _repo;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
|
|
||||||
public ActivityManager(
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IActivityRepository repo,
|
|
||||||
IUserManager userManager)
|
|
||||||
{
|
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ActivityManager));
|
|
||||||
_repo = repo;
|
|
||||||
_userManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
entry.Date = DateTime.UtcNow;
|
|
||||||
|
|
||||||
_repo.Create(entry);
|
|
||||||
|
|
||||||
EntryCreated?.Invoke(this, new GenericEventArgs<ActivityLogEntry>(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
|
|
||||||
|
|
||||||
foreach (var item in result.Items)
|
|
||||||
{
|
|
||||||
if (item.UserId == Guid.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = _userManager.GetUserById(item.UserId);
|
|
||||||
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
var dto = _userManager.GetUserDto(user);
|
|
||||||
item.UserPrimaryImageTag = dto.PrimaryImageTag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
return GetActivityLogEntries(minDate, null, startIndex, limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,313 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Emby.Server.Implementations.Data;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Model.Activity;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SQLitePCL.pretty;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Activity
|
|
||||||
{
|
|
||||||
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
|
|
||||||
{
|
|
||||||
private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
|
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
|
|
||||||
public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
|
|
||||||
: base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
|
|
||||||
{
|
|
||||||
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
|
|
||||||
_fileSystem = fileSystem;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
InitializeInternal();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error loading database file. Will reset and retry.");
|
|
||||||
|
|
||||||
_fileSystem.DeleteFile(DbFilePath);
|
|
||||||
|
|
||||||
InitializeInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeInternal()
|
|
||||||
{
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunQueries(new[]
|
|
||||||
{
|
|
||||||
"create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
|
|
||||||
"drop index if exists idx_ActivityLogEntries"
|
|
||||||
});
|
|
||||||
|
|
||||||
TryMigrate(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryMigrate(ManagedConnection connection)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (TableExists(connection, "ActivityLogEntries"))
|
|
||||||
{
|
|
||||||
connection.RunQueries(new[]
|
|
||||||
{
|
|
||||||
"INSERT INTO ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) SELECT Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity FROM ActivityLogEntries",
|
|
||||||
"drop table if exists ActivityLogEntries"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error migrating activity log database");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
|
|
||||||
|
|
||||||
public void Create(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
if (entry == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
|
||||||
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
statement.TryBindNull("@UserId");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
|
||||||
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(ActivityLogEntry entry)
|
|
||||||
{
|
|
||||||
if (entry == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(entry));
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(db =>
|
|
||||||
{
|
|
||||||
using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
|
|
||||||
{
|
|
||||||
statement.TryBind("@Id", entry.Id);
|
|
||||||
|
|
||||||
statement.TryBind("@Name", entry.Name);
|
|
||||||
statement.TryBind("@Overview", entry.Overview);
|
|
||||||
statement.TryBind("@ShortOverview", entry.ShortOverview);
|
|
||||||
statement.TryBind("@Type", entry.Type);
|
|
||||||
statement.TryBind("@ItemId", entry.ItemId);
|
|
||||||
|
|
||||||
if (entry.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
statement.TryBindNull("@UserId");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
|
|
||||||
statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
|
|
||||||
statement.TryBind("@LogSeverity", entry.Severity.ToString());
|
|
||||||
|
|
||||||
statement.MoveNext();
|
|
||||||
}
|
|
||||||
}, TransactionMode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
|
|
||||||
{
|
|
||||||
var commandText = BaseActivitySelectText;
|
|
||||||
var whereClauses = new List<string>();
|
|
||||||
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
whereClauses.Add("DateCreated>=@DateCreated");
|
|
||||||
}
|
|
||||||
if (hasUserId.HasValue)
|
|
||||||
{
|
|
||||||
if (hasUserId.Value)
|
|
||||||
{
|
|
||||||
whereClauses.Add("UserId not null");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
whereClauses.Add("UserId is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
if (startIndex.HasValue && startIndex.Value > 0)
|
|
||||||
{
|
|
||||||
var pagingWhereText = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
whereClauses.Add(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"Id NOT IN (SELECT Id FROM ActivityLog {0} ORDER BY DateCreated DESC LIMIT {1})",
|
|
||||||
pagingWhereText,
|
|
||||||
startIndex.Value));
|
|
||||||
}
|
|
||||||
|
|
||||||
var whereText = whereClauses.Count == 0 ?
|
|
||||||
string.Empty :
|
|
||||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
|
||||||
|
|
||||||
commandText += whereText;
|
|
||||||
|
|
||||||
commandText += " ORDER BY DateCreated DESC";
|
|
||||||
|
|
||||||
if (limit.HasValue)
|
|
||||||
{
|
|
||||||
commandText += " LIMIT " + limit.Value.ToString(_usCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
var statementTexts = new[]
|
|
||||||
{
|
|
||||||
commandText,
|
|
||||||
"select count (Id) from ActivityLog" + whereTextWithoutPaging
|
|
||||||
};
|
|
||||||
|
|
||||||
var list = new List<ActivityLogEntry>();
|
|
||||||
var result = new QueryResult<ActivityLogEntry>();
|
|
||||||
|
|
||||||
using (var connection = GetConnection(true))
|
|
||||||
{
|
|
||||||
connection.RunInTransaction(
|
|
||||||
db =>
|
|
||||||
{
|
|
||||||
var statements = PrepareAll(db, statementTexts).ToList();
|
|
||||||
|
|
||||||
using (var statement = statements[0])
|
|
||||||
{
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var row in statement.ExecuteQuery())
|
|
||||||
{
|
|
||||||
list.Add(GetEntry(row));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
using (var statement = statements[1])
|
|
||||||
{
|
|
||||||
if (minDate.HasValue)
|
|
||||||
{
|
|
||||||
statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReadTransactionMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Items = list;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ActivityLogEntry GetEntry(IReadOnlyList<IResultSetValue> reader)
|
|
||||||
{
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
var info = new ActivityLogEntry
|
|
||||||
{
|
|
||||||
Id = reader[index].ToInt64()
|
|
||||||
};
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Name = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Overview = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.ShortOverview = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Type = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.ItemId = reader[index].ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.UserId = new Guid(reader[index].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
|
||||||
info.Date = reader[index].ReadDateTime();
|
|
||||||
|
|
||||||
index++;
|
|
||||||
if (reader[index].SQLiteType != SQLiteType.Null)
|
|
||||||
{
|
|
||||||
info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,7 @@ using MediaBrowser.Common.Configuration;
|
||||||
namespace Emby.Server.Implementations.AppBase
|
namespace Emby.Server.Implementations.AppBase
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides a base class to hold common application paths used by both the Ui and Server.
|
/// Provides a base class to hold common application paths used by both the UI and Server.
|
||||||
/// This can be subclassed to add application-specific paths.
|
/// This can be subclassed to add application-specific paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseApplicationPaths : IApplicationPaths
|
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||||
|
@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="programDataPath">The program data path.</param>
|
||||||
|
/// <param name="logDirectoryPath">The log directory path.</param>
|
||||||
|
/// <param name="configurationDirectoryPath">The configuration directory path.</param>
|
||||||
|
/// <param name="cacheDirectoryPath">The cache directory path.</param>
|
||||||
|
/// <param name="webDirectoryPath">The web directory path.</param>
|
||||||
protected BaseApplicationPaths(
|
protected BaseApplicationPaths(
|
||||||
string programDataPath,
|
string programDataPath,
|
||||||
string logDirectoryPath,
|
string logDirectoryPath,
|
||||||
|
@ -37,10 +42,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// <value>The program data path.</value>
|
/// <value>The program data path.</value>
|
||||||
public string ProgramDataPath { get; }
|
public string ProgramDataPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the path to the web UI resources folder.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The web UI resources path.</value>
|
|
||||||
public string WebPath { get; }
|
public string WebPath { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
configuration = Activator.CreateInstance(type);
|
configuration = Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = new MemoryStream())
|
using var stream = new MemoryStream();
|
||||||
|
xmlSerializer.SerializeToStream(configuration, stream);
|
||||||
|
|
||||||
|
// Take the object we just got and serialize it back to bytes
|
||||||
|
var newBytes = stream.ToArray();
|
||||||
|
|
||||||
|
// If the file didn't exist before, or if something has changed, re-save
|
||||||
|
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
||||||
{
|
{
|
||||||
xmlSerializer.SerializeToStream(configuration, stream);
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
// Take the object we just got and serialize it back to bytes
|
// Save it after load in case we got new items
|
||||||
var newBytes = stream.ToArray();
|
File.WriteAllBytes(path, newBytes);
|
||||||
|
|
||||||
// If the file didn't exist before, or if something has changed, re-save
|
|
||||||
if (buffer == null || !buffer.SequenceEqual(newBytes))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
||||||
|
|
||||||
// Save it after load in case we got new items
|
|
||||||
File.WriteAllBytes(path, newBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return configuration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,10 +22,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAll(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -36,67 +34,61 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = ReaderFactory.Open(source))
|
using var reader = ReaderFactory.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true
|
||||||
options.ExtractFullPath = true;
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
if (overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
options.Overwrite = true;
|
options.Overwrite = true;
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = ZipReader.Open(source))
|
using var reader = ZipReader.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true,
|
||||||
options.ExtractFullPath = true;
|
Overwrite = overwriteExistingFiles
|
||||||
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using var reader = GZipReader.Open(source);
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
var options = new ExtractionOptions();
|
ExtractFullPath = true,
|
||||||
options.ExtractFullPath = true;
|
Overwrite = overwriteExistingFiles
|
||||||
|
};
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
||||||
{
|
{
|
||||||
using (var reader = GZipReader.Open(source))
|
using var reader = GZipReader.Open(source);
|
||||||
|
if (reader.MoveToNextEntry())
|
||||||
{
|
{
|
||||||
if (reader.MoveToNextEntry())
|
var entry = reader.Entry;
|
||||||
{
|
|
||||||
var entry = reader.Entry;
|
|
||||||
|
|
||||||
var filename = entry.Key;
|
var filename = entry.Key;
|
||||||
if (string.IsNullOrWhiteSpace(filename))
|
if (string.IsNullOrWhiteSpace(filename))
|
||||||
{
|
{
|
||||||
filename = defaultFileName;
|
filename = defaultFileName;
|
||||||
}
|
|
||||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,10 +100,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -122,21 +112,15 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var archive = SevenZipArchive.Open(source))
|
using var archive = SevenZipArchive.Open(source);
|
||||||
|
using var reader = archive.ExtractAllEntries();
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
using (var reader = archive.ExtractAllEntries())
|
ExtractFullPath = true,
|
||||||
{
|
Overwrite = overwriteExistingFiles
|
||||||
var options = new ExtractionOptions();
|
};
|
||||||
options.ExtractFullPath = true;
|
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -147,10 +131,8 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var fileStream = File.OpenRead(sourceFile))
|
using var fileStream = File.OpenRead(sourceFile);
|
||||||
{
|
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
||||||
ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -161,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
|
||||||
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
/// <param name="overwriteExistingFiles">if set to <c>true</c> [overwrite existing files].</param>
|
||||||
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
|
||||||
{
|
{
|
||||||
using (var archive = TarArchive.Open(source))
|
using var archive = TarArchive.Open(source);
|
||||||
|
using var reader = archive.ExtractAllEntries();
|
||||||
|
var options = new ExtractionOptions
|
||||||
{
|
{
|
||||||
using (var reader = archive.ExtractAllEntries())
|
ExtractFullPath = true,
|
||||||
{
|
Overwrite = overwriteExistingFiles
|
||||||
var options = new ExtractionOptions();
|
};
|
||||||
options.ExtractFullPath = true;
|
|
||||||
|
|
||||||
if (overwriteExistingFiles)
|
reader.WriteAllToDirectory(targetPath, options);
|
||||||
{
|
|
||||||
options.Overwrite = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Model.Branding;
|
using MediaBrowser.Model.Branding;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Branding
|
namespace Emby.Server.Implementations.Branding
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A configuration factory for <see cref="BrandingOptions"/>.
|
||||||
|
/// </summary>
|
||||||
public class BrandingConfigurationFactory : IConfigurationFactory
|
public class BrandingConfigurationFactory : IConfigurationFactory
|
||||||
{
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
|
|
|
@ -1,51 +1,48 @@
|
||||||
using System;
|
using System;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Browser
|
namespace Emby.Server.Implementations.Browser
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class BrowserLauncher.
|
/// Assists in opening application URLs in an external browser.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class BrowserLauncher
|
public static class BrowserLauncher
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the dashboard page.
|
/// Opens the home page of the web client.
|
||||||
/// </summary>
|
|
||||||
/// <param name="page">The page.</param>
|
|
||||||
/// <param name="appHost">The app host.</param>
|
|
||||||
private static void OpenDashboardPage(string page, IServerApplicationHost appHost)
|
|
||||||
{
|
|
||||||
var url = appHost.GetLocalApiUrl("localhost") + "/web/" + page;
|
|
||||||
|
|
||||||
OpenUrl(appHost, url);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Opens the web client.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The app host.</param>
|
/// <param name="appHost">The app host.</param>
|
||||||
public static void OpenWebApp(IServerApplicationHost appHost)
|
public static void OpenWebApp(IServerApplicationHost appHost)
|
||||||
{
|
{
|
||||||
OpenDashboardPage("index.html", appHost);
|
TryOpenUrl(appHost, "/web/index.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Opens the URL.
|
/// Opens the swagger API page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="appHost">The application host instance.</param>
|
/// <param name="appHost">The app host.</param>
|
||||||
/// <param name="url">The URL.</param>
|
public static void OpenSwaggerPage(IServerApplicationHost appHost)
|
||||||
private static void OpenUrl(IServerApplicationHost appHost, string url)
|
{
|
||||||
|
TryOpenUrl(appHost, "/swagger/index.html");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Opens the specified URL in an external browser window. Any exceptions will be logged, but ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="appHost">The application host.</param>
|
||||||
|
/// <param name="relativeUrl">The URL to open, relative to the server base URL.</param>
|
||||||
|
private static void TryOpenUrl(IServerApplicationHost appHost, string relativeUrl)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
appHost.LaunchUrl(url);
|
string baseUrl = appHost.GetLocalApiUrl("localhost");
|
||||||
|
appHost.LaunchUrl(baseUrl + relativeUrl);
|
||||||
}
|
}
|
||||||
catch (NotSupportedException)
|
catch (Exception ex)
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
{
|
||||||
|
var logger = appHost.Resolve<ILogger>();
|
||||||
|
logger?.LogError(ex, "Failed to open browser window with URL {URL}", relativeUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
|
@ -11,6 +10,9 @@ using MediaBrowser.Model.Dto;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A media source provider for channels.
|
||||||
|
/// </summary>
|
||||||
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
||||||
{
|
{
|
||||||
private readonly ChannelManager _channelManager;
|
private readonly ChannelManager _channelManager;
|
||||||
|
@ -27,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (item.SourceType == SourceType.Channel)
|
return item.SourceType == SourceType.Channel
|
||||||
{
|
? _channelManager.GetDynamicMediaSources(item, cancellationToken)
|
||||||
return _channelManager.GetDynamicMediaSources(item, cancellationToken);
|
: Task.FromResult(Enumerable.Empty<MediaSourceInfo>());
|
||||||
}
|
|
||||||
|
|
||||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -11,20 +9,32 @@ using MediaBrowser.Model.Entities;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An image provider for channels.
|
||||||
|
/// </summary>
|
||||||
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
|
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelImageProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelManager">The channel manager.</param>
|
||||||
public ChannelImageProvider(IChannelManager channelManager)
|
public ChannelImageProvider(IChannelManager channelManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Name => "Channel Image Provider";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetChannel(item).GetSupportedChannelImages();
|
return GetChannel(item).GetSupportedChannelImages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
|
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channel = GetChannel(item);
|
var channel = GetChannel(item);
|
||||||
|
@ -32,8 +42,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channel.GetChannelImage(type, cancellationToken);
|
return channel.GetChannelImage(type, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Channel Image Provider";
|
/// <inheritdoc />
|
||||||
|
|
||||||
public bool Supports(BaseItem item)
|
public bool Supports(BaseItem item)
|
||||||
{
|
{
|
||||||
return item is Channel;
|
return item is Channel;
|
||||||
|
@ -46,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
|
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -29,10 +27,11 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The LiveTV channel manager.
|
||||||
|
/// </summary>
|
||||||
public class ChannelManager : IChannelManager
|
public class ChannelManager : IChannelManager
|
||||||
{
|
{
|
||||||
internal IChannel[] Channels { get; private set; }
|
|
||||||
|
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly IUserDataManager _userDataManager;
|
private readonly IUserDataManager _userDataManager;
|
||||||
private readonly IDtoService _dtoService;
|
private readonly IDtoService _dtoService;
|
||||||
|
@ -43,11 +42,28 @@ namespace Emby.Server.Implementations.Channels
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IProviderManager _providerManager;
|
private readonly IProviderManager _providerManager;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
||||||
|
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelManager"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userManager">The user manager.</param>
|
||||||
|
/// <param name="dtoService">The dto service.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="loggerFactory">The logger factory.</param>
|
||||||
|
/// <param name="config">The server configuration manager.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
/// <param name="userDataManager">The user data manager.</param>
|
||||||
|
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||||
|
/// <param name="providerManager">The provider manager.</param>
|
||||||
public ChannelManager(
|
public ChannelManager(
|
||||||
IUserManager userManager,
|
IUserManager userManager,
|
||||||
IDtoService dtoService,
|
IDtoService dtoService,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ILoggerFactory loggerFactory,
|
ILogger<ChannelManager> logger,
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IUserDataManager userDataManager,
|
IUserDataManager userDataManager,
|
||||||
|
@ -57,7 +73,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_logger = loggerFactory.CreateLogger(nameof(ChannelManager));
|
_logger = logger;
|
||||||
_config = config;
|
_config = config;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
|
@ -65,13 +81,17 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_providerManager = providerManager;
|
_providerManager = providerManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal IChannel[] Channels { get; private set; }
|
||||||
|
|
||||||
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public void AddParts(IEnumerable<IChannel> channels)
|
public void AddParts(IEnumerable<IChannel> channels)
|
||||||
{
|
{
|
||||||
Channels = channels.ToArray();
|
Channels = channels.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool EnableMediaSourceDisplay(BaseItem item)
|
public bool EnableMediaSourceDisplay(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -80,15 +100,16 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return !(channel is IDisableMediaSourceDisplay);
|
return !(channel is IDisableMediaSourceDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool CanDelete(BaseItem item)
|
public bool CanDelete(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
|
||||||
|
|
||||||
var supportsDelete = channel as ISupportsDelete;
|
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
|
||||||
return supportsDelete != null && supportsDelete.CanDelete(item);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool EnableMediaProbe(BaseItem item)
|
public bool EnableMediaProbe(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -97,6 +118,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return channel is ISupportsMediaProbe;
|
return channel is ISupportsMediaProbe;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Task DeleteItem(BaseItem item)
|
public Task DeleteItem(BaseItem item)
|
||||||
{
|
{
|
||||||
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
|
||||||
|
@ -123,11 +145,16 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.OrderBy(i => i.Name);
|
.OrderBy(i => i.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the installed channel IDs.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An <see cref="IEnumerable{T}"/> containing installed channel IDs.</returns>
|
||||||
public IEnumerable<Guid> GetInstalledChannelIds()
|
public IEnumerable<Guid> GetInstalledChannelIds()
|
||||||
{
|
{
|
||||||
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
|
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
public QueryResult<Channel> GetChannelsInternal(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(Guid.Empty)
|
||||||
|
@ -146,15 +173,13 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
|
return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
|
||||||
|
&& hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
||||||
return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +196,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,9 +212,9 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsFavorite.HasValue)
|
if (query.IsFavorite.HasValue)
|
||||||
{
|
{
|
||||||
var val = query.IsFavorite.Value;
|
var val = query.IsFavorite.Value;
|
||||||
|
@ -215,7 +239,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +249,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
all = all.Skip(query.StartIndex.Value).ToList();
|
all = all.Skip(query.StartIndex.Value).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.Limit.HasValue)
|
if (query.Limit.HasValue)
|
||||||
{
|
{
|
||||||
all = all.Take(query.Limit.Value).ToList();
|
all = all.Take(query.Limit.Value).ToList();
|
||||||
|
@ -248,6 +272,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
public QueryResult<BaseItemDto> GetChannels(ChannelQuery query)
|
||||||
{
|
{
|
||||||
var user = query.UserId.Equals(Guid.Empty)
|
var user = query.UserId.Equals(Guid.Empty)
|
||||||
|
@ -256,11 +281,9 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
var internalResult = GetChannelsInternal(query);
|
var internalResult = GetChannelsInternal(query);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions()
|
var dtoOptions = new DtoOptions();
|
||||||
{
|
|
||||||
};
|
|
||||||
|
|
||||||
//TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
// TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
|
||||||
|
|
||||||
var result = new QueryResult<BaseItemDto>
|
var result = new QueryResult<BaseItemDto>
|
||||||
|
@ -272,6 +295,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the associated channels.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||||
|
/// <returns>The completed task.</returns>
|
||||||
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var allChannelsList = GetAllChannels().ToList();
|
var allChannelsList = GetAllChannels().ToList();
|
||||||
|
@ -305,14 +334,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private Channel GetChannelEntity(IChannel channel)
|
private Channel GetChannelEntity(IChannel channel)
|
||||||
{
|
{
|
||||||
var item = GetChannel(GetInternalChannelId(channel.Name));
|
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
|
||||||
|
|
||||||
if (item == null)
|
|
||||||
{
|
|
||||||
item = GetChannel(channel, CancellationToken.None).Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
|
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item)
|
||||||
|
@ -341,8 +363,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -351,6 +373,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_jsonSerializer.SerializeToFile(mediaSources, path);
|
_jsonSerializer.SerializeToFile(mediaSources, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
|
IEnumerable<MediaSourceInfo> results = GetSavedMediaSources(item);
|
||||||
|
@ -360,16 +383,20 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the dynamic media sources based on the provided item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="cancellationToken">A cancellation token that can be used to cancel the operation.</param>
|
||||||
|
/// <returns>The task representing the operation to get the media sources.</returns>
|
||||||
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
|
public async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channel = GetChannel(item.ChannelId);
|
var channel = GetChannel(item.ChannelId);
|
||||||
var channelPlugin = GetChannelProvider(channel);
|
var channelPlugin = GetChannelProvider(channel);
|
||||||
|
|
||||||
var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
|
|
||||||
|
|
||||||
IEnumerable<MediaSourceInfo> results;
|
IEnumerable<MediaSourceInfo> results;
|
||||||
|
|
||||||
if (requiresCallback != null)
|
if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
|
||||||
{
|
{
|
||||||
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
@ -384,9 +411,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
|
|
||||||
new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
|
|
||||||
|
|
||||||
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
|
||||||
|
@ -409,7 +433,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
|
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
|
||||||
{
|
{
|
||||||
info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
|
info.RunTimeTicks ??= item.RunTimeTicks;
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -444,18 +468,21 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.Path = path;
|
item.Path = path;
|
||||||
|
|
||||||
if (!item.ChannelId.Equals(id))
|
if (!item.ChannelId.Equals(id))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = id;
|
item.ChannelId = id;
|
||||||
|
|
||||||
if (item.ParentId != parentFolderId)
|
if (item.ParentId != parentFolderId)
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
|
||||||
|
@ -472,51 +499,56 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_libraryManager.CreateItem(item, null);
|
_libraryManager.CreateItem(item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
await item.RefreshMetadata(
|
||||||
{
|
new MetadataRefreshOptions(new DirectoryService(_fileSystem))
|
||||||
ForceSave = !isNew && forceUpdate
|
{
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
ForceSave = !isNew && forceUpdate
|
||||||
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetOfficialRating(ChannelParentalRating rating)
|
private static string GetOfficialRating(ChannelParentalRating rating)
|
||||||
{
|
{
|
||||||
switch (rating)
|
return rating switch
|
||||||
{
|
{
|
||||||
case ChannelParentalRating.Adult:
|
ChannelParentalRating.Adult => "XXX",
|
||||||
return "XXX";
|
ChannelParentalRating.UsR => "R",
|
||||||
case ChannelParentalRating.UsR:
|
ChannelParentalRating.UsPG13 => "PG-13",
|
||||||
return "R";
|
ChannelParentalRating.UsPG => "PG",
|
||||||
case ChannelParentalRating.UsPG13:
|
_ => null
|
||||||
return "PG-13";
|
};
|
||||||
case ChannelParentalRating.UsPG:
|
|
||||||
return "PG";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a channel with the provided Guid.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The Guid.</param>
|
||||||
|
/// <returns>The corresponding channel.</returns>
|
||||||
public Channel GetChannel(Guid id)
|
public Channel GetChannel(Guid id)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemById(id) as Channel;
|
return _libraryManager.GetItemById(id) as Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public Channel GetChannel(string id)
|
public Channel GetChannel(string id)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemById(id) as Channel;
|
return _libraryManager.GetItemById(id) as Channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ChannelFeatures[] GetAllChannelFeatures()
|
public ChannelFeatures[] GetAllChannelFeatures()
|
||||||
{
|
{
|
||||||
return _libraryManager.GetItemIds(new InternalItemsQuery
|
return _libraryManager.GetItemIds(
|
||||||
{
|
new InternalItemsQuery
|
||||||
IncludeItemTypes = new[] { typeof(Channel).Name },
|
{
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
IncludeItemTypes = new[] { typeof(Channel).Name },
|
||||||
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
|
||||||
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public ChannelFeatures GetChannelFeatures(string id)
|
public ChannelFeatures GetChannelFeatures(string id)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
|
@ -530,15 +562,27 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
|
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the provided Guid supports external transfer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelId">The Guid.</param>
|
||||||
|
/// <returns>Whether or not the provided Guid supports external transfer.</returns>
|
||||||
public bool SupportsExternalTransfer(Guid channelId)
|
public bool SupportsExternalTransfer(Guid channelId)
|
||||||
{
|
{
|
||||||
//var channel = GetChannel(channelId);
|
|
||||||
var channelProvider = GetChannelProvider(channelId);
|
var channelProvider = GetChannelProvider(channelId);
|
||||||
|
|
||||||
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
|
/// <summary>
|
||||||
|
/// Gets the provided channel's supported features.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channel">The channel.</param>
|
||||||
|
/// <param name="provider">The provider.</param>
|
||||||
|
/// <param name="features">The features.</param>
|
||||||
|
/// <returns>The supported features.</returns>
|
||||||
|
public ChannelFeatures GetChannelFeaturesDto(
|
||||||
|
Channel channel,
|
||||||
IChannel provider,
|
IChannel provider,
|
||||||
InternalChannelFeatures features)
|
InternalChannelFeatures features)
|
||||||
{
|
{
|
||||||
|
@ -567,9 +611,11 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(name));
|
throw new ArgumentNullException(nameof(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
|
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -588,6 +634,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
|
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
|
||||||
|
@ -614,7 +661,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
query.IsFolder = false;
|
query.IsFolder = false;
|
||||||
|
|
||||||
// hack for trailers, figure out a better way later
|
// hack for trailers, figure out a better way later
|
||||||
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
|
var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
|
||||||
|
|
||||||
if (sortByPremiereDate)
|
if (sortByPremiereDate)
|
||||||
{
|
{
|
||||||
|
@ -640,10 +687,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var query = new InternalItemsQuery();
|
var query = new InternalItemsQuery
|
||||||
query.Parent = internalChannel;
|
{
|
||||||
query.EnableTotalRecordCount = false;
|
Parent = internalChannel,
|
||||||
query.ChannelIds = new Guid[] { internalChannel.Id };
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
|
};
|
||||||
|
|
||||||
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -651,17 +700,20 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
if (item is Folder folder)
|
if (item is Folder folder)
|
||||||
{
|
{
|
||||||
await GetChannelItemsInternal(new InternalItemsQuery
|
await GetChannelItemsInternal(
|
||||||
{
|
new InternalItemsQuery
|
||||||
Parent = folder,
|
{
|
||||||
EnableTotalRecordCount = false,
|
Parent = folder,
|
||||||
ChannelIds = new Guid[] { internalChannel.Id }
|
EnableTotalRecordCount = false,
|
||||||
|
ChannelIds = new Guid[] { internalChannel.Id }
|
||||||
}, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
new SimpleProgress<double>(),
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Get the internal channel entity
|
// Get the internal channel entity
|
||||||
|
@ -672,7 +724,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
|
||||||
|
|
||||||
var itemsResult = await GetChannelItems(channelProvider,
|
var itemsResult = await GetChannelItems(
|
||||||
|
channelProvider,
|
||||||
query.User,
|
query.User,
|
||||||
parentItem is Channel ? null : parentItem.ExternalId,
|
parentItem is Channel ? null : parentItem.ExternalId,
|
||||||
null,
|
null,
|
||||||
|
@ -684,13 +737,12 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
query.Parent = channel;
|
query.Parent = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ChannelIds = Array.Empty<Guid>();
|
query.ChannelIds = Array.Empty<Guid>();
|
||||||
|
|
||||||
// Not yet sure why this is causing a problem
|
// Not yet sure why this is causing a problem
|
||||||
query.GroupByPresentationUniqueKey = false;
|
query.GroupByPresentationUniqueKey = false;
|
||||||
|
|
||||||
//_logger.LogDebug("GetChannelItemsInternal");
|
|
||||||
|
|
||||||
// null if came from cache
|
// null if came from cache
|
||||||
if (itemsResult != null)
|
if (itemsResult != null)
|
||||||
{
|
{
|
||||||
|
@ -707,12 +759,15 @@ namespace Emby.Server.Implementations.Channels
|
||||||
var deadItem = _libraryManager.GetItemById(deadId);
|
var deadItem = _libraryManager.GetItemById(deadId);
|
||||||
if (deadItem != null)
|
if (deadItem != null)
|
||||||
{
|
{
|
||||||
_libraryManager.DeleteItem(deadItem, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
deadItem,
|
||||||
DeleteFileLocation = false,
|
new DeleteOptions
|
||||||
DeleteFromExternalProvider = false
|
{
|
||||||
|
DeleteFileLocation = false,
|
||||||
}, parentItem, false);
|
DeleteFromExternalProvider = false
|
||||||
|
},
|
||||||
|
parentItem,
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -720,6 +775,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return _libraryManager.GetItemsResult(query);
|
return _libraryManager.GetItemsResult(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
public async Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -735,7 +791,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
|
|
||||||
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
|
||||||
User user,
|
User user,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
|
@ -743,7 +798,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
bool sortDescending,
|
bool sortDescending,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
|
var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
var cacheLength = CacheLength;
|
var cacheLength = CacheLength;
|
||||||
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
|
||||||
|
@ -761,11 +816,9 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
@ -785,16 +838,14 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException)
|
catch (FileNotFoundException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (IOException)
|
catch (IOException)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = new InternalChannelItemQuery
|
var query = new InternalChannelItemQuery
|
||||||
{
|
{
|
||||||
UserId = user == null ? Guid.Empty : user.Id,
|
UserId = user?.Id ?? Guid.Empty,
|
||||||
SortBy = sortField,
|
SortBy = sortField,
|
||||||
SortDescending = sortDescending,
|
SortDescending = sortDescending,
|
||||||
FolderId = externalFolderId
|
FolderId = externalFolderId
|
||||||
|
@ -833,7 +884,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetChannelDataCachePath(IChannel channel,
|
private string GetChannelDataCachePath(
|
||||||
|
IChannel channel,
|
||||||
string userId,
|
string userId,
|
||||||
string externalFolderId,
|
string externalFolderId,
|
||||||
ChannelItemSortField? sortField,
|
ChannelItemSortField? sortField,
|
||||||
|
@ -843,8 +895,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
var userCacheKey = string.Empty;
|
var userCacheKey = string.Empty;
|
||||||
|
|
||||||
var hasCacheKey = channel as IHasCacheKey;
|
if (channel is IHasCacheKey hasCacheKey)
|
||||||
if (hasCacheKey != null)
|
|
||||||
{
|
{
|
||||||
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
@ -858,6 +909,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
filename += "-sortField-" + sortField.Value;
|
filename += "-sortField-" + sortField.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sortDescending)
|
if (sortDescending)
|
||||||
{
|
{
|
||||||
filename += "-sortDescending";
|
filename += "-sortDescending";
|
||||||
|
@ -865,7 +917,8 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
return Path.Combine(_config.ApplicationPaths.CachePath,
|
return Path.Combine(
|
||||||
|
_config.ApplicationPaths.CachePath,
|
||||||
"channels",
|
"channels",
|
||||||
channelId,
|
channelId,
|
||||||
version,
|
version,
|
||||||
|
@ -919,60 +972,32 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
if (info.Type == ChannelItemType.Folder)
|
if (info.Type == ChannelItemType.Folder)
|
||||||
{
|
{
|
||||||
if (info.FolderType == ChannelFolderType.MusicAlbum)
|
item = info.FolderType switch
|
||||||
{
|
{
|
||||||
item = GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew);
|
ChannelFolderType.MusicAlbum => GetItemById<MusicAlbum>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
ChannelFolderType.MusicArtist => GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew),
|
||||||
else if (info.FolderType == ChannelFolderType.MusicArtist)
|
ChannelFolderType.PhotoAlbum => GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew),
|
||||||
{
|
ChannelFolderType.Series => GetItemById<Series>(info.Id, channelProvider.Name, out isNew),
|
||||||
item = GetItemById<MusicArtist>(info.Id, channelProvider.Name, out isNew);
|
ChannelFolderType.Season => GetItemById<Season>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
_ => GetItemById<Folder>(info.Id, channelProvider.Name, out isNew)
|
||||||
else if (info.FolderType == ChannelFolderType.PhotoAlbum)
|
};
|
||||||
{
|
|
||||||
item = GetItemById<PhotoAlbum>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else if (info.FolderType == ChannelFolderType.Series)
|
|
||||||
{
|
|
||||||
item = GetItemById<Series>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else if (info.FolderType == ChannelFolderType.Season)
|
|
||||||
{
|
|
||||||
item = GetItemById<Season>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Folder>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (info.MediaType == ChannelMediaType.Audio)
|
else if (info.MediaType == ChannelMediaType.Audio)
|
||||||
{
|
{
|
||||||
if (info.ContentType == ChannelMediaContentType.Podcast)
|
item = info.ContentType == ChannelMediaContentType.Podcast
|
||||||
{
|
? GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew)
|
||||||
item = GetItemById<AudioBook>(info.Id, channelProvider.Name, out isNew);
|
: GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Audio>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (info.ContentType == ChannelMediaContentType.Episode)
|
item = info.ContentType switch
|
||||||
{
|
{
|
||||||
item = GetItemById<Episode>(info.Id, channelProvider.Name, out isNew);
|
ChannelMediaContentType.Episode => GetItemById<Episode>(info.Id, channelProvider.Name, out isNew),
|
||||||
}
|
ChannelMediaContentType.Movie => GetItemById<Movie>(info.Id, channelProvider.Name, out isNew),
|
||||||
else if (info.ContentType == ChannelMediaContentType.Movie)
|
var x when x == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer
|
||||||
{
|
=> GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew),
|
||||||
item = GetItemById<Movie>(info.Id, channelProvider.Name, out isNew);
|
_ => GetItemById<Video>(info.Id, channelProvider.Name, out isNew)
|
||||||
}
|
};
|
||||||
else if (info.ContentType == ChannelMediaContentType.Trailer || info.ExtraType == ExtraType.Trailer)
|
|
||||||
{
|
|
||||||
item = GetItemById<Trailer>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
item = GetItemById<Video>(info.Id, channelProvider.Name, out isNew);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
|
var enableMediaProbe = channelProvider is ISupportsMediaProbe;
|
||||||
|
@ -981,7 +1006,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
item.RunTimeTicks = null;
|
item.RunTimeTicks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (isNew || !enableMediaProbe)
|
else if (isNew || !enableMediaProbe)
|
||||||
{
|
{
|
||||||
item.RunTimeTicks = info.RunTimeTicks;
|
item.RunTimeTicks = info.RunTimeTicks;
|
||||||
|
@ -1014,26 +1038,24 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasArtists = item as IHasArtist;
|
if (item is IHasArtist hasArtists)
|
||||||
if (hasArtists != null)
|
|
||||||
{
|
{
|
||||||
hasArtists.Artists = info.Artists.ToArray();
|
hasArtists.Artists = info.Artists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||||
if (hasAlbumArtists != null)
|
|
||||||
{
|
{
|
||||||
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
var trailer = item as Trailer;
|
if (item is Trailer trailer)
|
||||||
if (trailer != null)
|
|
||||||
{
|
{
|
||||||
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
trailer.TrailerTypes = info.TrailerTypes.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,6 +1079,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ChannelId = internalChannelId;
|
item.ChannelId = internalChannelId;
|
||||||
|
|
||||||
if (!item.ParentId.Equals(parentFolderId))
|
if (!item.ParentId.Equals(parentFolderId))
|
||||||
|
@ -1064,16 +1087,17 @@ namespace Emby.Server.Implementations.Channels
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ParentId = parentFolderId;
|
item.ParentId = parentFolderId;
|
||||||
|
|
||||||
var hasSeries = item as IHasSeries;
|
if (item is IHasSeries hasSeries)
|
||||||
if (hasSeries != null)
|
|
||||||
{
|
{
|
||||||
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasSeries.SeriesName = info.SeriesName;
|
hasSeries.SeriesName = info.SeriesName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1082,24 +1106,23 @@ namespace Emby.Server.Implementations.Channels
|
||||||
forceUpdate = true;
|
forceUpdate = true;
|
||||||
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.ExternalId = info.Id;
|
item.ExternalId = info.Id;
|
||||||
|
|
||||||
var channelAudioItem = item as Audio;
|
if (item is Audio channelAudioItem)
|
||||||
if (channelAudioItem != null)
|
|
||||||
{
|
{
|
||||||
channelAudioItem.ExtraType = info.ExtraType;
|
channelAudioItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelVideoItem = item as Video;
|
if (item is Video channelVideoItem)
|
||||||
if (channelVideoItem != null)
|
|
||||||
{
|
{
|
||||||
channelVideoItem.ExtraType = info.ExtraType;
|
channelVideoItem.ExtraType = info.ExtraType;
|
||||||
|
|
||||||
var mediaSource = info.MediaSources.FirstOrDefault();
|
var mediaSource = info.MediaSources.FirstOrDefault();
|
||||||
item.Path = mediaSource == null ? null : mediaSource.Path;
|
item.Path = mediaSource?.Path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
|
||||||
|
@ -1156,7 +1179,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
|
if (isNew || forceUpdate || item.DateLastRefreshed == default)
|
||||||
{
|
{
|
||||||
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -11,21 +9,34 @@ using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Channels
|
namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A task to remove all non-installed channels from the database.
|
||||||
|
/// </summary>
|
||||||
public class ChannelPostScanTask
|
public class ChannelPostScanTask
|
||||||
{
|
{
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ChannelPostScanTask"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="channelManager">The channel manager.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
_channelManager = channelManager;
|
_channelManager = channelManager;
|
||||||
_userManager = userManager;
|
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Runs this task.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">The progress.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
|
/// <returns>The completed task.</returns>
|
||||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CleanDatabase(cancellationToken);
|
CleanDatabase(cancellationToken);
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user