mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-07-30 17:25:40 +02:00
Merge branch 'master' into sessionmanager
This commit is contained in:
commit
0a43814596
96
.ci/azure-pipelines-compat.yml
Normal file
96
.ci/azure-pipelines-compat.yml
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
parameters:
|
||||||
|
- name: Packages
|
||||||
|
type: object
|
||||||
|
default: {}
|
||||||
|
- name: LinuxImage
|
||||||
|
type: string
|
||||||
|
default: "ubuntu-latest"
|
||||||
|
- name: DotNetSdkVersion
|
||||||
|
type: string
|
||||||
|
default: 3.1.100
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: CompatibilityCheck
|
||||||
|
displayName: Compatibility Check
|
||||||
|
pool:
|
||||||
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
|
# only execute for pull requests
|
||||||
|
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
${{ each Package in parameters.Packages }}:
|
||||||
|
${{ Package.key }}:
|
||||||
|
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||||
|
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||||
|
maxParallel: 2
|
||||||
|
dependsOn: MainBuild
|
||||||
|
steps:
|
||||||
|
- checkout: none
|
||||||
|
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: "Update DotNet"
|
||||||
|
inputs:
|
||||||
|
packageType: sdk
|
||||||
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: "Download New Assembly Build Artifact"
|
||||||
|
inputs:
|
||||||
|
source: "current"
|
||||||
|
artifact: "$(NugetPackageName)"
|
||||||
|
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
||||||
|
runVersion: "latest"
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: "Copy New Assembly Build Artifact"
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
||||||
|
contents: "**/*.dll"
|
||||||
|
targetFolder: $(System.ArtifactsDirectory)/new-release
|
||||||
|
cleanTargetFolder: true
|
||||||
|
overWrite: true
|
||||||
|
flattenFolders: true
|
||||||
|
|
||||||
|
- task: DownloadPipelineArtifact@2
|
||||||
|
displayName: "Download Reference Assembly Build Artifact"
|
||||||
|
inputs:
|
||||||
|
source: "specific"
|
||||||
|
artifact: "$(NugetPackageName)"
|
||||||
|
path: "$(System.ArtifactsDirectory)/current-artifacts"
|
||||||
|
project: "$(System.TeamProjectId)"
|
||||||
|
pipeline: "$(System.DefinitionId)"
|
||||||
|
runVersion: "latestFromBranch"
|
||||||
|
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: "Copy Reference Assembly Build Artifact"
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts
|
||||||
|
contents: "**/*.dll"
|
||||||
|
targetFolder: $(System.ArtifactsDirectory)/current-release
|
||||||
|
cleanTargetFolder: true
|
||||||
|
overWrite: true
|
||||||
|
flattenFolders: true
|
||||||
|
|
||||||
|
- task: DownloadGitHubRelease@0
|
||||||
|
displayName: "Download ABI Compatibility Check Tool"
|
||||||
|
inputs:
|
||||||
|
connection: Jellyfin Release Download
|
||||||
|
userRepository: EraYaN/dotnet-compatibility
|
||||||
|
defaultVersionType: "latest"
|
||||||
|
itemPattern: "**-ci.zip"
|
||||||
|
downloadPath: "$(System.ArtifactsDirectory)"
|
||||||
|
|
||||||
|
- task: ExtractFiles@1
|
||||||
|
displayName: "Extract ABI Compatibility Check Tool"
|
||||||
|
inputs:
|
||||||
|
archiveFilePatterns: "$(System.ArtifactsDirectory)/*-ci.zip"
|
||||||
|
destinationFolder: $(System.ArtifactsDirectory)/tools
|
||||||
|
cleanDestinationFolder: true
|
||||||
|
|
||||||
|
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Execute ABI Compatibility Check Tool"
|
||||||
|
inputs:
|
||||||
|
script: "dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only"
|
||||||
|
workingDirectory: $(System.ArtifactsDirectory)
|
101
.ci/azure-pipelines-main.yml
Normal file
101
.ci/azure-pipelines-main.yml
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
parameters:
|
||||||
|
LinuxImage: "ubuntu-latest"
|
||||||
|
RestoreBuildProjects: "Jellyfin.Server/Jellyfin.Server.csproj"
|
||||||
|
DotNetSdkVersion: 3.1.100
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: MainBuild
|
||||||
|
displayName: Main Build
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
Release:
|
||||||
|
BuildConfiguration: Release
|
||||||
|
Debug:
|
||||||
|
BuildConfiguration: Debug
|
||||||
|
maxParallel: 2
|
||||||
|
pool:
|
||||||
|
vmImage: "${{ parameters.LinuxImage }}"
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: true
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Clone Web Client (Master, Release, or Tag)"
|
||||||
|
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Clone Web Client (PR)"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||||
|
inputs:
|
||||||
|
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||||
|
|
||||||
|
- task: NodeTool@0
|
||||||
|
displayName: "Install Node"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
versionSpec: "10.x"
|
||||||
|
|
||||||
|
- task: CmdLine@2
|
||||||
|
displayName: "Build Web Client"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
script: yarn install
|
||||||
|
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||||
|
|
||||||
|
- task: CopyFiles@2
|
||||||
|
displayName: "Copy Web Client"
|
||||||
|
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||||
|
inputs:
|
||||||
|
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
||||||
|
contents: "**"
|
||||||
|
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||||
|
cleanTargetFolder: true
|
||||||
|
overWrite: true
|
||||||
|
flattenFolders: false
|
||||||
|
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: "Update DotNet"
|
||||||
|
inputs:
|
||||||
|
packageType: sdk
|
||||||
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: "Publish Server"
|
||||||
|
inputs:
|
||||||
|
command: publish
|
||||||
|
publishWebProjects: false
|
||||||
|
projects: "${{ parameters.RestoreBuildProjects }}"
|
||||||
|
arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
|
||||||
|
zipAfterPublish: false
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: "Publish Artifact Naming"
|
||||||
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||||
|
artifactName: "Jellyfin.Naming"
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: "Publish Artifact Controller"
|
||||||
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||||
|
artifactName: "Jellyfin.Controller"
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: "Publish Artifact Model"
|
||||||
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||||
|
artifactName: "Jellyfin.Model"
|
||||||
|
|
||||||
|
- task: PublishPipelineArtifact@0
|
||||||
|
displayName: "Publish Artifact Common"
|
||||||
|
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||||
|
inputs:
|
||||||
|
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||||
|
artifactName: "Jellyfin.Common"
|
65
.ci/azure-pipelines-test.yml
Normal file
65
.ci/azure-pipelines-test.yml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
parameters:
|
||||||
|
- name: ImageNames
|
||||||
|
type: object
|
||||||
|
default:
|
||||||
|
Linux: "ubuntu-latest"
|
||||||
|
Windows: "windows-latest"
|
||||||
|
macOS: "macos-latest"
|
||||||
|
- name: TestProjects
|
||||||
|
type: string
|
||||||
|
default: "tests/**/*Tests.csproj"
|
||||||
|
- name: DotNetSdkVersion
|
||||||
|
type: string
|
||||||
|
default: 3.1.100
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: MainTest
|
||||||
|
displayName: Main Test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
${{ each imageName in parameters.ImageNames }}:
|
||||||
|
${{ imageName.key }}:
|
||||||
|
ImageName: ${{ imageName.value }}
|
||||||
|
maxParallel: 3
|
||||||
|
pool:
|
||||||
|
vmImage: "$(ImageName)"
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
clean: true
|
||||||
|
submodules: true
|
||||||
|
persistCredentials: false
|
||||||
|
|
||||||
|
- task: UseDotNet@2
|
||||||
|
displayName: "Update DotNet"
|
||||||
|
inputs:
|
||||||
|
packageType: sdk
|
||||||
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
|
- task: DotNetCoreCLI@2
|
||||||
|
displayName: Run .NET Core CLI tests
|
||||||
|
inputs:
|
||||||
|
command: "test"
|
||||||
|
projects: ${{ parameters.TestProjects }}
|
||||||
|
arguments: '--configuration Release --collect:"XPlat Code Coverage" --settings tests/coverletArgs.runsettings --verbosity minimal "-p:GenerateDocumentationFile=False"'
|
||||||
|
publishTestResults: true
|
||||||
|
testRunTitle: $(Agent.JobName)
|
||||||
|
workingDirectory: "$(Build.SourcesDirectory)"
|
||||||
|
|
||||||
|
- task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
|
||||||
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
|
displayName: ReportGenerator (merge)
|
||||||
|
inputs:
|
||||||
|
reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
|
||||||
|
targetdir: "$(Agent.TempDirectory)/merged/"
|
||||||
|
reporttypes: "Cobertura"
|
||||||
|
|
||||||
|
## V2 is already in the repository but it does not work "wrong number of segments" YAML error.
|
||||||
|
- task: PublishCodeCoverageResults@1
|
||||||
|
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
|
||||||
|
displayName: Publish Code Coverage
|
||||||
|
inputs:
|
||||||
|
codeCoverageTool: "cobertura"
|
||||||
|
#summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
|
||||||
|
summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
|
||||||
|
pathToSources: $(Build.SourcesDirectory)
|
||||||
|
failIfCoverageEmpty: true
|
82
.ci/azure-pipelines-windows.yml
Normal file
82
.ci/azure-pipelines-windows.yml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
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"
|
|
@ -2,9 +2,11 @@ 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
|
||||||
|
value: 3.1.100
|
||||||
|
|
||||||
pr:
|
pr:
|
||||||
autoCancel: true
|
autoCancel: true
|
||||||
|
@ -13,234 +15,26 @@ trigger:
|
||||||
batch: true
|
batch: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- job: main_build
|
- template: azure-pipelines-main.yml
|
||||||
displayName: Main Build
|
parameters:
|
||||||
pool:
|
LinuxImage: "ubuntu-latest"
|
||||||
vmImage: ubuntu-latest
|
RestoreBuildProjects: $(RestoreBuildProjects)
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Release:
|
|
||||||
BuildConfiguration: Release
|
|
||||||
Debug:
|
|
||||||
BuildConfiguration: Debug
|
|
||||||
maxParallel: 2
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
- template: azure-pipelines-test.yml
|
||||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
parameters:
|
||||||
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'))
|
ImageNames:
|
||||||
inputs:
|
Linux: "ubuntu-latest"
|
||||||
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
Windows: "windows-latest"
|
||||||
|
macOS: "macos-latest"
|
||||||
|
|
||||||
- task: CmdLine@2
|
- template: azure-pipelines-windows.yml
|
||||||
displayName: "Clone Web Client (PR)"
|
parameters:
|
||||||
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'))
|
WindowsImage: "windows-latest"
|
||||||
inputs:
|
TestProjects: $(TestProjects)
|
||||||
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
|
- template: azure-pipelines-compat.yml
|
||||||
displayName: 'Install Node'
|
parameters:
|
||||||
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'))
|
Packages:
|
||||||
inputs:
|
|
||||||
versionSpec: '10.x'
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Copy Web Client'
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
|
||||||
contents: '**'
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true # Optional
|
|
||||||
overWrite: true # Optional
|
|
||||||
flattenFolders: false # Optional
|
|
||||||
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Update DotNet'
|
|
||||||
inputs:
|
|
||||||
packageType: sdk
|
|
||||||
version: 3.1.100
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
|
||||||
displayName: 'Publish Server'
|
|
||||||
inputs:
|
|
||||||
command: publish
|
|
||||||
publishWebProjects: false
|
|
||||||
projects: '$(RestoreBuildProjects)'
|
|
||||||
arguments: '--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)'
|
|
||||||
zipAfterPublish: false
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: 'Publish Artifact Naming'
|
|
||||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll'
|
|
||||||
artifactName: 'Jellyfin.Naming'
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: 'Publish Artifact Controller'
|
|
||||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
|
|
||||||
artifactName: 'Jellyfin.Controller'
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: 'Publish Artifact Model'
|
|
||||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
|
|
||||||
artifactName: 'Jellyfin.Model'
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: 'Publish Artifact Common'
|
|
||||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll'
|
|
||||||
artifactName: 'Jellyfin.Common'
|
|
||||||
|
|
||||||
- job: main_test
|
|
||||||
displayName: Main Test
|
|
||||||
pool:
|
|
||||||
vmImage: windows-latest
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: false
|
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
|
||||||
displayName: Build
|
|
||||||
inputs:
|
|
||||||
command: build
|
|
||||||
publishWebProjects: false
|
|
||||||
projects: '$(TestProjects)'
|
|
||||||
arguments: '--configuration $(BuildConfiguration)'
|
|
||||||
zipAfterPublish: false
|
|
||||||
|
|
||||||
- task: VisualStudioTestPlatformInstaller@1
|
|
||||||
inputs:
|
|
||||||
packageFeedSelector: 'nugetOrg' # Options: nugetOrg, customFeed, netShare
|
|
||||||
versionSelector: 'latestPreRelease' # Required when packageFeedSelector == NugetOrg || PackageFeedSelector == CustomFeed# Options: latestPreRelease, latestStable, specificVersion
|
|
||||||
- task: VSTest@2
|
|
||||||
inputs:
|
|
||||||
testSelector: 'testAssemblies' # Options: testAssemblies, testPlan, testRun
|
|
||||||
testAssemblyVer2: | # Required when testSelector == TestAssemblies
|
|
||||||
**\bin\$(BuildConfiguration)\**\*tests.dll
|
|
||||||
**\bin\$(BuildConfiguration)\**\*test.dll
|
|
||||||
!**\obj\**
|
|
||||||
!**\xunit.runner.visualstudio.testadapter.dll
|
|
||||||
!**\xunit.runner.visualstudio.dotnetcore.testadapter.dll
|
|
||||||
searchFolder: '$(System.DefaultWorkingDirectory)'
|
|
||||||
runInParallel: True # Optional
|
|
||||||
runTestsInIsolation: True # Optional
|
|
||||||
codeCoverageEnabled: True # Optional
|
|
||||||
configuration: 'Debug' # Optional
|
|
||||||
publishRunAttachments: true # Optional
|
|
||||||
testRunTitle: $(Agent.JobName)
|
|
||||||
otherConsoleOptions: '/platform:x64 /Framework:.NETCoreApp,Version=v3.1 /logger:console;verbosity="normal"'
|
|
||||||
|
|
||||||
- job: main_build_win
|
|
||||||
displayName: Publish Windows
|
|
||||||
pool:
|
|
||||||
vmImage: windows-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Release:
|
|
||||||
BuildConfiguration: Release
|
|
||||||
maxParallel: 2
|
|
||||||
steps:
|
|
||||||
- checkout: self
|
|
||||||
clean: true
|
|
||||||
submodules: true
|
|
||||||
persistCredentials: true
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "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')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: 'git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Clone Web Client (PR)"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
|
||||||
inputs:
|
|
||||||
script: 'git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web'
|
|
||||||
|
|
||||||
- task: NodeTool@0
|
|
||||||
displayName: 'Install Node'
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
versionSpec: '10.x'
|
|
||||||
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: "Build Web Client"
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
script: yarn install
|
|
||||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Copy Web Client'
|
|
||||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')) ,eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist # Optional
|
|
||||||
contents: '**'
|
|
||||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
|
||||||
cleanTargetFolder: true # Optional
|
|
||||||
overWrite: true # Optional
|
|
||||||
flattenFolders: false # Optional
|
|
||||||
|
|
||||||
- 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' # Optional. Options: filePath, inline
|
|
||||||
filePath: ./deployment/windows/build-jellyfin.ps1 # Required when targetType == FilePath
|
|
||||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
|
||||||
errorActionPreference: 'stop' # Optional. Options: stop, continue, silentlyContinue
|
|
||||||
workingDirectory: $(Build.SourcesDirectory) # Optional
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Copy NSIS Installer'
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/ # Optional
|
|
||||||
contents: 'jellyfin*.exe'
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
|
||||||
cleanTargetFolder: true # Optional
|
|
||||||
overWrite: true # Optional
|
|
||||||
flattenFolders: true # Optional
|
|
||||||
|
|
||||||
- task: PublishPipelineArtifact@0
|
|
||||||
displayName: 'Publish Artifact Setup'
|
|
||||||
condition: and(eq(variables['BuildConfiguration'], 'Release'), succeeded())
|
|
||||||
inputs:
|
|
||||||
targetPath: '$(build.artifactstagingdirectory)/setup'
|
|
||||||
artifactName: 'Jellyfin Server Setup'
|
|
||||||
|
|
||||||
- job: dotnet_compat
|
|
||||||
displayName: Compatibility Check
|
|
||||||
pool:
|
|
||||||
vmImage: ubuntu-latest
|
|
||||||
dependsOn: main_build
|
|
||||||
# only execute for pull requests
|
|
||||||
condition: and(succeeded(), variables['System.PullRequest.PullRequestNumber'])
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
Naming:
|
Naming:
|
||||||
NugetPackageName: Jellyfin.Naming
|
NugetPackageName: Jellyfin.Naming
|
||||||
AssemblyFileName: Emby.Naming.dll
|
AssemblyFileName: Emby.Naming.dll
|
||||||
|
@ -253,74 +47,4 @@ jobs:
|
||||||
Common:
|
Common:
|
||||||
NugetPackageName: Jellyfin.Common
|
NugetPackageName: Jellyfin.Common
|
||||||
AssemblyFileName: MediaBrowser.Common.dll
|
AssemblyFileName: MediaBrowser.Common.dll
|
||||||
maxParallel: 2
|
LinuxImage: "ubuntu-latest"
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
|
|
||||||
- task: UseDotNet@2
|
|
||||||
displayName: 'Update DotNet'
|
|
||||||
inputs:
|
|
||||||
packageType: sdk
|
|
||||||
version: 3.1.100
|
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: 'Download New Assembly Build Artifact'
|
|
||||||
inputs:
|
|
||||||
source: 'current' # Options: current, specific
|
|
||||||
artifact: '$(NugetPackageName)' # Optional
|
|
||||||
path: '$(System.ArtifactsDirectory)/new-artifacts'
|
|
||||||
runVersion: 'latest' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Copy New Assembly Build Artifact'
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts # Optional
|
|
||||||
contents: '**/*.dll'
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/new-release
|
|
||||||
cleanTargetFolder: true # Optional
|
|
||||||
overWrite: true # Optional
|
|
||||||
flattenFolders: true # Optional
|
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: 'Download Reference Assembly Build Artifact'
|
|
||||||
inputs:
|
|
||||||
source: 'specific' # Options: current, specific
|
|
||||||
artifact: '$(NugetPackageName)' # Optional
|
|
||||||
path: '$(System.ArtifactsDirectory)/current-artifacts'
|
|
||||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
|
||||||
pipeline: '$(System.DefinitionId)' # Required when source == Specific
|
|
||||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
|
||||||
runBranch: 'refs/heads/$(System.PullRequest.TargetBranch)' # Required when source == Specific && runVersion == LatestFromBranch
|
|
||||||
|
|
||||||
- task: CopyFiles@2
|
|
||||||
displayName: 'Copy Reference Assembly Build Artifact'
|
|
||||||
inputs:
|
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/current-artifacts # Optional
|
|
||||||
contents: '**/*.dll'
|
|
||||||
targetFolder: $(System.ArtifactsDirectory)/current-release
|
|
||||||
cleanTargetFolder: true # Optional
|
|
||||||
overWrite: true # Optional
|
|
||||||
flattenFolders: true # Optional
|
|
||||||
|
|
||||||
- task: DownloadGitHubRelease@0
|
|
||||||
displayName: 'Download ABI Compatibility Check Tool'
|
|
||||||
inputs:
|
|
||||||
connection: Jellyfin Release Download
|
|
||||||
userRepository: EraYaN/dotnet-compatibility
|
|
||||||
defaultVersionType: 'latest' # Options: latest, specificVersion, specificTag
|
|
||||||
itemPattern: '**-ci.zip' # Optional
|
|
||||||
downloadPath: '$(System.ArtifactsDirectory)'
|
|
||||||
|
|
||||||
- task: ExtractFiles@1
|
|
||||||
displayName: 'Extract ABI Compatibility Check Tool'
|
|
||||||
inputs:
|
|
||||||
archiveFilePatterns: '$(System.ArtifactsDirectory)/*-ci.zip'
|
|
||||||
destinationFolder: $(System.ArtifactsDirectory)/tools
|
|
||||||
cleanDestinationFolder: true
|
|
||||||
|
|
||||||
# The `--warnings-only` switch will swallow the return code and not emit any errors.
|
|
||||||
- task: CmdLine@2
|
|
||||||
displayName: 'Execute ABI Compatibility Check Tool'
|
|
||||||
inputs:
|
|
||||||
script: 'dotnet tools/CompatibilityCheckerCLI.dll current-release/$(AssemblyFileName) new-release/$(AssemblyFileName) --azure-pipelines --warnings-only'
|
|
||||||
workingDirectory: $(System.ArtifactsDirectory) # Optional
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
name: Nightly-$(date:yyyyMMdd).$(rev:r)
|
|
||||||
|
|
||||||
variables:
|
|
||||||
- name: Version
|
|
||||||
value: '1.0.0'
|
|
||||||
|
|
||||||
trigger: none
|
|
||||||
pr: none
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: publish_artifacts_nightly
|
|
||||||
displayName: Publish Artifacts Nightly
|
|
||||||
pool:
|
|
||||||
vmImage: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download the Windows Setup Artifact
|
|
||||||
inputs:
|
|
||||||
source: 'specific' # Options: current, specific
|
|
||||||
artifact: 'Jellyfin Server Setup' # Optional
|
|
||||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
|
||||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
|
||||||
pipelineId: 1 # Required when source == Specific
|
|
||||||
runVersion: 'latestFromBranch' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
|
||||||
runBranch: 'refs/heads/master' # Required when source == Specific && runVersion == LatestFromBranch
|
|
||||||
|
|
||||||
- task: SSH@0
|
|
||||||
displayName: 'Create Drop directory'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_nightly_azure_upload'
|
|
||||||
|
|
||||||
- task: CopyFilesOverSSH@0
|
|
||||||
displayName: 'Copy the Windows Setup to the Repo'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
|
||||||
contents: 'jellyfin_*.exe'
|
|
||||||
targetFolder: '/srv/incoming/jellyfin_nightly_azure_upload/win-installer'
|
|
||||||
|
|
||||||
- task: SSH@0
|
|
||||||
displayName: 'Clean up SCP symlink'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
commands: 'rm -f /srv/incoming/jellyfin_nightly_azure_upload'
|
|
|
@ -1,48 +0,0 @@
|
||||||
name: Release-$(Version)-$(date:yyyyMMdd).$(rev:r)
|
|
||||||
|
|
||||||
variables:
|
|
||||||
- name: Version
|
|
||||||
value: '1.0.0'
|
|
||||||
- name: UsedRunId
|
|
||||||
value: 0
|
|
||||||
|
|
||||||
trigger: none
|
|
||||||
pr: none
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
- job: publish_artifacts_release
|
|
||||||
displayName: Publish Artifacts Release
|
|
||||||
pool:
|
|
||||||
vmImage: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- checkout: none
|
|
||||||
- task: DownloadPipelineArtifact@2
|
|
||||||
displayName: Download the Windows Setup Artifact
|
|
||||||
inputs:
|
|
||||||
source: 'specific' # Options: current, specific
|
|
||||||
artifact: 'Jellyfin Server Setup' # Optional
|
|
||||||
path: '$(System.ArtifactsDirectory)/win-installer'
|
|
||||||
project: '$(System.TeamProjectId)' # Required when source == Specific
|
|
||||||
pipelineId: 1 # Required when source == Specific
|
|
||||||
runVersion: 'specific' # Required when source == Specific. Options: latest, latestFromBranch, specific
|
|
||||||
runId: $(UsedRunId)
|
|
||||||
|
|
||||||
- task: SSH@0
|
|
||||||
displayName: 'Create Drop directory'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
commands: 'mkdir -p /srv/incoming/jellyfin_$(Version)/win-installer && ln -s /srv/incoming/jellyfin_$(Version) /srv/incoming/jellyfin_release_azure_upload'
|
|
||||||
|
|
||||||
- task: CopyFilesOverSSH@0
|
|
||||||
displayName: 'Copy the Windows Setup to the Repo'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
sourceFolder: '$(System.ArtifactsDirectory)/win-installer'
|
|
||||||
contents: 'jellyfin_*.exe'
|
|
||||||
targetFolder: '/srv/incoming/jellyfin_release_azure_upload/win-installer'
|
|
||||||
|
|
||||||
- task: SSH@0
|
|
||||||
displayName: 'Clean up SCP symlink'
|
|
||||||
inputs:
|
|
||||||
sshEndpoint: 'Jellyfin Build Server'
|
|
||||||
commands: 'rm -f /srv/incoming/jellyfin_release_azure_upload'
|
|
|
@ -32,6 +32,7 @@
|
||||||
- [nevado](https://github.com/nevado)
|
- [nevado](https://github.com/nevado)
|
||||||
- [mark-monteiro](https://github.com/mark-monteiro)
|
- [mark-monteiro](https://github.com/mark-monteiro)
|
||||||
- [ullmie02](https://github.com/ullmie02)
|
- [ullmie02](https://github.com/ullmie02)
|
||||||
|
- [pR0Ps](https://github.com/pR0Ps)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||||
# mesa-va-drivers: needed for VAAPI
|
# mesa-va-drivers: needed for VAAPI
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||||
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl \
|
libfontconfig1 libgomp1 libva-drm2 mesa-va-drivers openssl ca-certificates \
|
||||||
&& apt-get clean autoclean \
|
&& apt-get clean autoclean \
|
||||||
&& apt-get autoremove \
|
&& apt-get autoremove \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# Requires binfm_misc registration
|
|
||||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
|
||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.1
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
FROM debian:buster-slim
|
||||||
FROM debian:stretch-slim-arm32v7
|
|
||||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||||
|
libssl-dev ca-certificates \
|
||||||
&& 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
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
# Requires binfm_misc registration
|
|
||||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
|
||||||
ARG DOTNET_VERSION=3.1
|
ARG DOTNET_VERSION=3.1
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,11 +21,10 @@ RUN find . -type d -name obj | xargs -r rm -r
|
||||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
|
||||||
|
|
||||||
|
|
||||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
FROM debian:buster-slim
|
||||||
FROM debian:stretch-slim-arm64v8
|
|
||||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
|
||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
|
||||||
|
libssl-dev ca-certificates \
|
||||||
&& 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
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.0</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
|
@ -42,7 +42,7 @@ namespace DvdLib.Ifo
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using (var vmgFs = _fileSystem.GetFileStream(vmgPath.FullName, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
using (var vmgFs = new FileStream(vmgPath.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||||
{
|
{
|
||||||
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
using (var vmgRead = new BigEndianBinaryReader(vmgFs))
|
||||||
{
|
{
|
||||||
|
@ -95,7 +95,7 @@ namespace DvdLib.Ifo
|
||||||
{
|
{
|
||||||
VTSPaths[vtsNum] = vtsPath;
|
VTSPaths[vtsNum] = vtsPath;
|
||||||
|
|
||||||
using (var vtsFs = _fileSystem.GetFileStream(vtsPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read))
|
using (var vtsFs = new FileStream(vtsPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||||
{
|
{
|
||||||
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
using (var vtsRead = new BigEndianBinaryReader(vtsFs))
|
||||||
{
|
{
|
||||||
|
|
|
@ -170,32 +170,32 @@ namespace Emby.Dlna.Api
|
||||||
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
return _resultFactory.GetResult(Request, xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(ProcessMediaReceiverRegistrarControlRequest request)
|
public async Task<object> Post(ProcessMediaReceiverRegistrarControlRequest request)
|
||||||
{
|
{
|
||||||
var response = PostAsync(request.RequestStream, MediaReceiverRegistrar);
|
var response = await PostAsync(request.RequestStream, MediaReceiverRegistrar).ConfigureAwait(false);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(ProcessContentDirectoryControlRequest request)
|
public async Task<object> Post(ProcessContentDirectoryControlRequest request)
|
||||||
{
|
{
|
||||||
var response = PostAsync(request.RequestStream, ContentDirectory);
|
var response = await PostAsync(request.RequestStream, ContentDirectory).ConfigureAwait(false);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public object Post(ProcessConnectionManagerControlRequest request)
|
public async Task<object> Post(ProcessConnectionManagerControlRequest request)
|
||||||
{
|
{
|
||||||
var response = PostAsync(request.RequestStream, ConnectionManager);
|
var response = await PostAsync(request.RequestStream, ConnectionManager).ConfigureAwait(false);
|
||||||
|
|
||||||
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
return _resultFactory.GetResult(Request, response.Xml, XMLContentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlResponse PostAsync(Stream requestStream, IUpnpService service)
|
private Task<ControlResponse> PostAsync(Stream requestStream, IUpnpService service)
|
||||||
{
|
{
|
||||||
var id = GetPathValue(2).ToString();
|
var id = GetPathValue(2).ToString();
|
||||||
|
|
||||||
return service.ProcessControlRequest(new ControlRequest
|
return service.ProcessControlRequestAsync(new ControlRequest
|
||||||
{
|
{
|
||||||
Headers = Request.Headers,
|
Headers = Request.Headers,
|
||||||
InputXml = requestStream,
|
InputXml = requestStream,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -20,17 +21,19 @@ namespace Emby.Dlna.ConnectionManager
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetServiceXml()
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new ConnectionManagerXmlBuilder().GetXml();
|
return new ConnectionManagerXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
/// <inheritdoc />
|
||||||
|
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
|
||||||
return new ControlHandler(_config, _logger, profile).ProcessControlRequest(request);
|
return new ControlHandler(_config, _logger, profile).ProcessControlRequestAsync(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -66,12 +67,14 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetServiceXml()
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new ContentDirectoryXmlBuilder().GetXml();
|
return new ContentDirectoryXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
/// <inheritdoc />
|
||||||
|
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
var profile = _dlna.GetProfile(request.Headers) ??
|
var profile = _dlna.GetProfile(request.Headers) ??
|
||||||
_dlna.GetDefaultProfile();
|
_dlna.GetDefaultProfile();
|
||||||
|
@ -96,7 +99,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
_userViewManager,
|
_userViewManager,
|
||||||
_mediaEncoder,
|
_mediaEncoder,
|
||||||
_tvSeriesManager)
|
_tvSeriesManager)
|
||||||
.ProcessControlRequest(request);
|
.ProcessControlRequestAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private User GetUser(DeviceProfile profile)
|
private User GetUser(DeviceProfile profile)
|
||||||
|
|
|
@ -76,7 +76,7 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams)
|
||||||
|
@ -771,11 +771,11 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
})
|
})
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
return new QueryResult<ServerItem>
|
return ApplyPaging(new QueryResult<ServerItem>
|
||||||
{
|
{
|
||||||
Items = folders,
|
Items = folders,
|
||||||
TotalRecordCount = folders.Length
|
TotalRecordCount = folders.Length
|
||||||
};
|
}, startIndex, limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
|
||||||
|
@ -1336,7 +1336,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
Logger.LogError("Error parsing item Id: {id}. Returning user root folder.", id);
|
||||||
|
|
||||||
return new ServerItem(_libraryManager.GetUserRootFolder());
|
return new ServerItem(_libraryManager.GetUserRootFolder());
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,6 @@ using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
public Filter(string filter)
|
public Filter(string filter)
|
||||||
{
|
{
|
||||||
_all = StringHelper.EqualsIgnoreCase(filter, "*");
|
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
_fields = (filter ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
}
|
||||||
|
|
|
@ -385,7 +385,7 @@ namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(systemProfilesPath);
|
Directory.CreateDirectory(systemProfilesPath);
|
||||||
|
|
||||||
using (var fileStream = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream);
|
await stream.CopyToAsync(fileStream);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Emby.Dlna
|
namespace Emby.Dlna
|
||||||
{
|
{
|
||||||
public interface IUpnpService
|
public interface IUpnpService
|
||||||
|
@ -13,6 +15,6 @@ namespace Emby.Dlna
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The request.</param>
|
/// <param name="request">The request.</param>
|
||||||
/// <returns>ControlResponse.</returns>
|
/// <returns>ControlResponse.</returns>
|
||||||
ControlResponse ProcessControlRequest(ControlRequest request);
|
Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Service;
|
using Emby.Dlna.Service;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -15,17 +16,19 @@ namespace Emby.Dlna.MediaReceiverRegistrar
|
||||||
_config = config;
|
_config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string GetServiceXml()
|
public string GetServiceXml()
|
||||||
{
|
{
|
||||||
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
return new MediaReceiverRegistrarXmlBuilder().GetXml();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
/// <inheritdoc />
|
||||||
|
public Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
return new ControlHandler(
|
return new ControlHandler(
|
||||||
_config,
|
_config,
|
||||||
Logger)
|
Logger)
|
||||||
.ProcessControlRequest(request);
|
.ProcessControlRequestAsync(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Server
|
namespace Emby.Dlna.Server
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
@ -15,44 +15,34 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
protected readonly IServerConfigurationManager Config;
|
protected IServerConfigurationManager Config { get; }
|
||||||
protected readonly ILogger _logger;
|
protected ILogger Logger { get; }
|
||||||
|
|
||||||
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
protected BaseControlHandler(IServerConfigurationManager config, ILogger logger)
|
||||||
{
|
{
|
||||||
Config = config;
|
Config = config;
|
||||||
_logger = logger;
|
Logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControlResponse ProcessControlRequest(ControlRequest request)
|
public async Task<ControlResponse> ProcessControlRequestAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var enableDebugLogging = Config.GetDlnaConfiguration().EnableDebugLog;
|
LogRequest(request);
|
||||||
|
|
||||||
if (enableDebugLogging)
|
|
||||||
{
|
|
||||||
LogRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
var response = ProcessControlRequestInternal(request);
|
|
||||||
|
|
||||||
if (enableDebugLogging)
|
|
||||||
{
|
|
||||||
LogResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var response = await ProcessControlRequestInternalAsync(request).ConfigureAwait(false);
|
||||||
|
LogResponse(response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error processing control request");
|
Logger.LogError(ex, "Error processing control request");
|
||||||
|
|
||||||
return new ControlErrorHandler().GetResponse(ex);
|
return ControlErrorHandler.GetResponse(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlResponse ProcessControlRequestInternal(ControlRequest request)
|
private async Task<ControlResponse> ProcessControlRequestInternalAsync(ControlRequest request)
|
||||||
{
|
{
|
||||||
ControlRequestInfo requestInfo = null;
|
ControlRequestInfo requestInfo = null;
|
||||||
|
|
||||||
|
@ -63,16 +53,17 @@ namespace Emby.Dlna.Service
|
||||||
ValidationType = ValidationType.None,
|
ValidationType = ValidationType.None,
|
||||||
CheckCharacters = false,
|
CheckCharacters = false,
|
||||||
IgnoreProcessingInstructions = true,
|
IgnoreProcessingInstructions = true,
|
||||||
IgnoreComments = true
|
IgnoreComments = true,
|
||||||
|
Async = true
|
||||||
};
|
};
|
||||||
|
|
||||||
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
using (var reader = XmlReader.Create(streamReader, readerSettings))
|
||||||
{
|
{
|
||||||
requestInfo = ParseRequest(reader);
|
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Received control request {0}", requestInfo.LocalName);
|
Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
|
||||||
|
|
||||||
var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
|
var result = GetResult(requestInfo.LocalName, requestInfo.Headers);
|
||||||
|
|
||||||
|
@ -114,17 +105,15 @@ namespace Emby.Dlna.Service
|
||||||
IsSuccessful = true
|
IsSuccessful = true
|
||||||
};
|
};
|
||||||
|
|
||||||
//logger.LogDebug(xml);
|
|
||||||
|
|
||||||
controlResponse.Headers.Add("EXT", string.Empty);
|
controlResponse.Headers.Add("EXT", string.Empty);
|
||||||
|
|
||||||
return controlResponse;
|
return controlResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlRequestInfo ParseRequest(XmlReader reader)
|
private async Task<ControlRequestInfo> ParseRequestAsync(XmlReader reader)
|
||||||
{
|
{
|
||||||
reader.MoveToContent();
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// Loop through each element
|
// Loop through each element
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||||
|
@ -139,37 +128,38 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
using (var subReader = reader.ReadSubtree())
|
using (var subReader = reader.ReadSubtree())
|
||||||
{
|
{
|
||||||
return ParseBodyTag(subReader);
|
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
reader.Skip();
|
await reader.SkipAsync().ConfigureAwait(false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ControlRequestInfo();
|
return new ControlRequestInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ControlRequestInfo ParseBodyTag(XmlReader reader)
|
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
|
||||||
{
|
{
|
||||||
var result = new ControlRequestInfo();
|
var result = new ControlRequestInfo();
|
||||||
|
|
||||||
reader.MoveToContent();
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// Loop through each element
|
// Loop through each element
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||||
|
@ -183,28 +173,28 @@ namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
using (var subReader = reader.ReadSubtree())
|
using (var subReader = reader.ReadSubtree())
|
||||||
{
|
{
|
||||||
ParseFirstBodyChild(subReader, result.Headers);
|
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseFirstBodyChild(XmlReader reader, IDictionary<string, string> headers)
|
private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
|
||||||
{
|
{
|
||||||
reader.MoveToContent();
|
await reader.MoveToContentAsync().ConfigureAwait(false);
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// Loop through each element
|
// Loop through each element
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
||||||
|
@ -212,20 +202,20 @@ namespace Emby.Dlna.Service
|
||||||
if (reader.NodeType == XmlNodeType.Element)
|
if (reader.NodeType == XmlNodeType.Element)
|
||||||
{
|
{
|
||||||
// TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
|
// TODO: Should we be doing this here, or should it be handled earlier when decoding the request?
|
||||||
headers[reader.LocalName.RemoveDiacritics()] = reader.ReadElementContentAsString();
|
headers[reader.LocalName.RemoveDiacritics()] = await reader.ReadElementContentAsStringAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
reader.Read();
|
await reader.ReadAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ControlRequestInfo
|
private class ControlRequestInfo
|
||||||
{
|
{
|
||||||
public string LocalName;
|
public string LocalName { get; set; }
|
||||||
public string NamespaceURI;
|
public string NamespaceURI { get; set; }
|
||||||
public IDictionary<string, string> Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
|
protected abstract IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, IDictionary<string, string> methodParams);
|
||||||
|
@ -237,10 +227,7 @@ namespace Emby.Dlna.Service
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var originalHeaders = request.Headers;
|
Logger.LogDebug("Control request. Headers: {@Headers}", request.Headers);
|
||||||
var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
|
||||||
|
|
||||||
_logger.LogDebug("Control request. Headers: {0}", headers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogResponse(ControlResponse response)
|
private void LogResponse(ControlResponse response)
|
||||||
|
@ -250,11 +237,7 @@ namespace Emby.Dlna.Service
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var originalHeaders = response.Headers;
|
Logger.LogDebug("Control response. Headers: {@Headers}\n{Xml}", response.Headers, response.Xml);
|
||||||
var headers = string.Join(", ", originalHeaders.Select(i => string.Format("{0}={1}", i.Key, i.Value)).ToArray());
|
|
||||||
//builder.Append(response.Xml);
|
|
||||||
|
|
||||||
_logger.LogDebug("Control response. Headers: {0}", headers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ using Emby.Dlna.Didl;
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
public class ControlErrorHandler
|
public static class ControlErrorHandler
|
||||||
{
|
{
|
||||||
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
private const string NS_SOAPENV = "http://schemas.xmlsoap.org/soap/envelope/";
|
||||||
|
|
||||||
public ControlResponse GetResponse(Exception ex)
|
public static ControlResponse GetResponse(Exception ex)
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Drawing;
|
using MediaBrowser.Model.Drawing;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -129,7 +128,7 @@ namespace Emby.Drawing
|
||||||
{
|
{
|
||||||
var file = await ProcessImage(options).ConfigureAwait(false);
|
var file = await ProcessImage(options).ConfigureAwait(false);
|
||||||
|
|
||||||
using (var fileStream = _fileSystem.GetFileStream(file.Item1, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
|
using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||||
{
|
{
|
||||||
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,15 +19,13 @@ namespace Emby.Naming.Audio
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiPartResult ParseMultiPart(string path)
|
public bool IsMultiPart(string path)
|
||||||
{
|
{
|
||||||
var result = new MultiPartResult();
|
|
||||||
|
|
||||||
var filename = Path.GetFileName(path);
|
var filename = Path.GetFileName(path);
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(filename))
|
if (string.IsNullOrEmpty(filename))
|
||||||
{
|
{
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this logic into options object
|
// TODO: Move this logic into options object
|
||||||
|
@ -57,12 +55,11 @@ namespace Emby.Naming.Audio
|
||||||
|
|
||||||
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
|
||||||
{
|
{
|
||||||
result.IsMultiPart = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Naming.Audio
|
|
||||||
{
|
|
||||||
public class MultiPartResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the part.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The part.</value>
|
|
||||||
public string Part { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance is multi part.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance is multi part; otherwise, <c>false</c>.</value>
|
|
||||||
public bool IsMultiPart { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,7 +32,7 @@ namespace Emby.Naming.AudioBook
|
||||||
public int? ChapterNumber { get; set; }
|
public int? ChapterNumber { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets a value indicating whether this instance is a directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
|
@ -39,9 +39,7 @@ namespace Emby.Naming.AudioBook
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = new StackResolver(_options)
|
||||||
.ResolveAudioBooks(metadata);
|
.ResolveAudioBooks(metadata);
|
||||||
|
|
||||||
var list = new List<AudioBookInfo>();
|
foreach (var stack in stackResult)
|
||||||
|
|
||||||
foreach (var stack in stackResult.Stacks)
|
|
||||||
{
|
{
|
||||||
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();
|
||||||
|
@ -50,20 +48,9 @@ namespace Emby.Naming.AudioBook
|
||||||
Files = stackFiles,
|
Files = stackFiles,
|
||||||
Name = stack.Name
|
Name = stack.Name
|
||||||
};
|
};
|
||||||
list.Add(info);
|
|
||||||
|
yield return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whatever files are left, just add them
|
|
||||||
/*list.AddRange(remainingFiles.Select(i => new AudioBookInfo
|
|
||||||
{
|
|
||||||
Files = new List<AudioBookFileInfo> { i },
|
|
||||||
Name = i.,
|
|
||||||
Year = i.Year
|
|
||||||
}));*/
|
|
||||||
|
|
||||||
var orderedList = list.OrderBy(i => i.Name);
|
|
||||||
|
|
||||||
return orderedList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,24 @@ namespace Emby.Naming.Common
|
||||||
private string _expression;
|
private string _expression;
|
||||||
private Regex _regex;
|
private Regex _regex;
|
||||||
|
|
||||||
|
public EpisodeExpression(string expression, bool byDate)
|
||||||
|
{
|
||||||
|
Expression = expression;
|
||||||
|
IsByDate = byDate;
|
||||||
|
DateTimeFormats = Array.Empty<string>();
|
||||||
|
SupportsAbsoluteEpisodeNumbers = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeExpression(string expression)
|
||||||
|
: this(expression, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public EpisodeExpression()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public string Expression
|
public string Expression
|
||||||
{
|
{
|
||||||
get => _expression;
|
get => _expression;
|
||||||
|
@ -32,23 +50,5 @@ 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 ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
|
||||||
|
|
||||||
public EpisodeExpression(string expression, bool byDate)
|
|
||||||
{
|
|
||||||
Expression = expression;
|
|
||||||
IsByDate = byDate;
|
|
||||||
DateTimeFormats = Array.Empty<string>();
|
|
||||||
SupportsAbsoluteEpisodeNumbers = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EpisodeExpression(string expression)
|
|
||||||
: this(expression, false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public EpisodeExpression()
|
|
||||||
: this(null)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,46 +11,6 @@ namespace Emby.Naming.Common
|
||||||
{
|
{
|
||||||
public class NamingOptions
|
public class NamingOptions
|
||||||
{
|
{
|
||||||
public string[] AudioFileExtensions { get; set; }
|
|
||||||
|
|
||||||
public string[] AlbumStackingPrefixes { get; set; }
|
|
||||||
|
|
||||||
public string[] SubtitleFileExtensions { get; set; }
|
|
||||||
|
|
||||||
public char[] SubtitleFlagDelimiters { get; set; }
|
|
||||||
|
|
||||||
public string[] SubtitleForcedFlags { get; set; }
|
|
||||||
|
|
||||||
public string[] SubtitleDefaultFlags { get; set; }
|
|
||||||
|
|
||||||
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
|
||||||
|
|
||||||
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
|
||||||
|
|
||||||
public string[] EpisodeMultiPartExpressions { get; set; }
|
|
||||||
|
|
||||||
public string[] VideoFileExtensions { get; set; }
|
|
||||||
|
|
||||||
public string[] StubFileExtensions { get; set; }
|
|
||||||
|
|
||||||
public string[] AudioBookPartsExpressions { get; set; }
|
|
||||||
|
|
||||||
public StubTypeRule[] StubTypes { get; set; }
|
|
||||||
|
|
||||||
public char[] VideoFlagDelimiters { get; set; }
|
|
||||||
|
|
||||||
public Format3DRule[] Format3DRules { get; set; }
|
|
||||||
|
|
||||||
public string[] VideoFileStackingExpressions { get; set; }
|
|
||||||
|
|
||||||
public string[] CleanDateTimes { get; set; }
|
|
||||||
|
|
||||||
public string[] CleanStrings { get; set; }
|
|
||||||
|
|
||||||
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
|
||||||
|
|
||||||
public ExtraRule[] VideoExtraRules { get; set; }
|
|
||||||
|
|
||||||
public NamingOptions()
|
public NamingOptions()
|
||||||
{
|
{
|
||||||
VideoFileExtensions = new[]
|
VideoFileExtensions = new[]
|
||||||
|
@ -177,13 +137,12 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
{
|
{
|
||||||
@"[ _\,\.\(\)\[\]\-](ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
@"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multisubs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r3|r5|bd5|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|x264|h264|xvid|xvidvd|xxx|www.www|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||||
@"[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
|
||||||
@"(\[.*\])"
|
@"(\[.*\])"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -340,7 +299,7 @@ namespace Emby.Naming.Common
|
||||||
|
|
||||||
// *** End Kodi Standard Naming
|
// *** End Kodi Standard Naming
|
||||||
|
|
||||||
// [bar] Foo - 1 [baz]
|
// [bar] Foo - 1 [baz]
|
||||||
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
@ -682,11 +641,54 @@ namespace Emby.Naming.Common
|
||||||
Compile();
|
Compile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string[] AudioFileExtensions { get; set; }
|
||||||
|
|
||||||
|
public string[] AlbumStackingPrefixes { get; set; }
|
||||||
|
|
||||||
|
public string[] SubtitleFileExtensions { get; set; }
|
||||||
|
|
||||||
|
public char[] SubtitleFlagDelimiters { get; set; }
|
||||||
|
|
||||||
|
public string[] SubtitleForcedFlags { get; set; }
|
||||||
|
|
||||||
|
public string[] SubtitleDefaultFlags { get; set; }
|
||||||
|
|
||||||
|
public EpisodeExpression[] EpisodeExpressions { get; set; }
|
||||||
|
|
||||||
|
public string[] EpisodeWithoutSeasonExpressions { get; set; }
|
||||||
|
|
||||||
|
public string[] EpisodeMultiPartExpressions { get; set; }
|
||||||
|
|
||||||
|
public string[] VideoFileExtensions { get; set; }
|
||||||
|
|
||||||
|
public string[] StubFileExtensions { get; set; }
|
||||||
|
|
||||||
|
public string[] AudioBookPartsExpressions { get; set; }
|
||||||
|
|
||||||
|
public StubTypeRule[] StubTypes { get; set; }
|
||||||
|
|
||||||
|
public char[] VideoFlagDelimiters { get; set; }
|
||||||
|
|
||||||
|
public Format3DRule[] Format3DRules { get; set; }
|
||||||
|
|
||||||
|
public string[] VideoFileStackingExpressions { get; set; }
|
||||||
|
|
||||||
|
public string[] CleanDateTimes { get; set; }
|
||||||
|
|
||||||
|
public string[] CleanStrings { get; set; }
|
||||||
|
|
||||||
|
public EpisodeExpression[] MultipleEpisodeExpressions { get; set; }
|
||||||
|
|
||||||
|
public ExtraRule[] VideoExtraRules { get; set; }
|
||||||
|
|
||||||
public Regex[] VideoFileStackingRegexes { get; private set; }
|
public Regex[] VideoFileStackingRegexes { get; private set; }
|
||||||
|
|
||||||
public Regex[] CleanDateTimeRegexes { get; private set; }
|
public Regex[] CleanDateTimeRegexes { get; private set; }
|
||||||
|
|
||||||
public Regex[] CleanStringRegexes { get; private set; }
|
public Regex[] CleanStringRegexes { get; private set; }
|
||||||
|
|
||||||
public Regex[] EpisodeWithoutSeasonRegexes { get; private set; }
|
public Regex[] EpisodeWithoutSeasonRegexes { get; private set; }
|
||||||
|
|
||||||
public Regex[] EpisodeMultiPartRegexes { get; private set; }
|
public Regex[] EpisodeMultiPartRegexes { get; private set; }
|
||||||
|
|
||||||
public void Compile()
|
public void Compile()
|
||||||
|
|
|
@ -4,9 +4,6 @@
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
@ -27,7 +24,7 @@
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers-->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
<!-- TODO: <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" /> -->
|
||||||
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
|
||||||
|
|
|
@ -31,7 +31,6 @@ namespace Emby.Naming.Subtitles
|
||||||
}
|
}
|
||||||
|
|
||||||
var flags = GetFlags(path);
|
var flags = GetFlags(path);
|
||||||
|
|
||||||
var info = new SubtitleInfo
|
var info = new SubtitleInfo
|
||||||
{
|
{
|
||||||
Path = path,
|
Path = path,
|
||||||
|
@ -45,7 +44,7 @@ namespace Emby.Naming.Subtitles
|
||||||
// Should have a name, language and file extension
|
// Should have a name, language and file extension
|
||||||
if (parts.Count >= 3)
|
if (parts.Count >= 3)
|
||||||
{
|
{
|
||||||
info.Language = parts[parts.Count - 2];
|
info.Language = parts[^2];
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -28,7 +29,7 @@ namespace Emby.Naming.TV
|
||||||
path += ".mp4";
|
path += ".mp4";
|
||||||
}
|
}
|
||||||
|
|
||||||
EpisodePathParserResult result = null;
|
EpisodePathParserResult? result = null;
|
||||||
|
|
||||||
foreach (var expression in _options.EpisodeExpressions)
|
foreach (var expression in _options.EpisodeExpressions)
|
||||||
{
|
{
|
||||||
|
@ -131,12 +132,12 @@ namespace Emby.Naming.TV
|
||||||
var endingNumberGroup = match.Groups["endingepnumber"];
|
var endingNumberGroup = match.Groups["endingepnumber"];
|
||||||
if (endingNumberGroup.Success)
|
if (endingNumberGroup.Success)
|
||||||
{
|
{
|
||||||
// Will only set EndingEpsiodeNumber if the captured number is not followed by additional numbers
|
// Will only set EndingEpisodeNumber if the captured number is not followed by additional numbers
|
||||||
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
// or a 'p' or 'i' as what you would get with a pixel resolution specification.
|
||||||
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
// It avoids erroneous parsing of something like "series-s09e14-1080p.mkv" as a multi-episode from E14 to E108
|
||||||
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
int nextIndex = endingNumberGroup.Index + endingNumberGroup.Length;
|
||||||
if (nextIndex >= name.Length
|
if (nextIndex >= name.Length
|
||||||
|| "0123456789iIpP".IndexOf(name[nextIndex]) == -1)
|
|| !"0123456789iIpP".Contains(name[nextIndex], StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
if (int.TryParse(endingNumberGroup.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out num))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -18,7 +19,7 @@ namespace Emby.Naming.TV
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EpisodeInfo Resolve(
|
public EpisodeInfo? Resolve(
|
||||||
string path,
|
string path,
|
||||||
bool isDirectory,
|
bool isDirectory,
|
||||||
bool? isNamed = null,
|
bool? isNamed = null,
|
||||||
|
@ -26,14 +27,9 @@ namespace Emby.Naming.TV
|
||||||
bool? supportsAbsoluteNumbers = null,
|
bool? supportsAbsoluteNumbers = null,
|
||||||
bool fillExtendedInfo = true)
|
bool fillExtendedInfo = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isStub = false;
|
bool isStub = false;
|
||||||
string container = null;
|
string? container = null;
|
||||||
string stubType = null;
|
string? stubType = null;
|
||||||
|
|
||||||
if (!isDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
|
@ -41,17 +37,13 @@ namespace Emby.Naming.TV
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var stubResult = StubResolver.ResolveFile(path, _options);
|
|
||||||
|
|
||||||
isStub = stubResult.IsStub;
|
|
||||||
|
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!isStub)
|
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
stubType = stubResult.StubType;
|
isStub = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
container = extension.TrimStart('.');
|
container = extension.TrimStart('.');
|
||||||
|
|
|
@ -8,9 +8,24 @@ using System.Linq;
|
||||||
|
|
||||||
namespace Emby.Naming.TV
|
namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
public class SeasonPathParser
|
public static class SeasonPathParser
|
||||||
{
|
{
|
||||||
public SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
/// <summary>
|
||||||
|
/// A season folder must contain one of these somewhere in the name.
|
||||||
|
/// </summary>
|
||||||
|
private static readonly string[] _seasonFolderNames =
|
||||||
|
{
|
||||||
|
"season",
|
||||||
|
"sæson",
|
||||||
|
"temporada",
|
||||||
|
"saison",
|
||||||
|
"staffel",
|
||||||
|
"series",
|
||||||
|
"сезон",
|
||||||
|
"stagione"
|
||||||
|
};
|
||||||
|
|
||||||
|
public static SeasonPathParserResult Parse(string path, bool supportSpecialAliases, bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var result = new SeasonPathParserResult();
|
var result = new SeasonPathParserResult();
|
||||||
|
|
||||||
|
@ -27,21 +42,6 @@ namespace Emby.Naming.TV
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A season folder must contain one of these somewhere in the name.
|
|
||||||
/// </summary>
|
|
||||||
private static readonly string[] _seasonFolderNames =
|
|
||||||
{
|
|
||||||
"season",
|
|
||||||
"sæson",
|
|
||||||
"temporada",
|
|
||||||
"saison",
|
|
||||||
"staffel",
|
|
||||||
"series",
|
|
||||||
"сезон",
|
|
||||||
"stagione"
|
|
||||||
};
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the season number from path.
|
/// Gets the season number from path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -150,6 +150,7 @@ namespace Emby.Naming.TV
|
||||||
{
|
{
|
||||||
numericStart = i;
|
numericStart = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,11 +162,11 @@ namespace Emby.Naming.TV
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentChar = path[i];
|
var currentChar = path[i];
|
||||||
if (currentChar.Equals('('))
|
if (currentChar == '(')
|
||||||
{
|
{
|
||||||
hasOpenParenth = true;
|
hasOpenParenth = true;
|
||||||
}
|
}
|
||||||
else if (currentChar.Equals(')'))
|
else if (currentChar == ')')
|
||||||
{
|
{
|
||||||
hasOpenParenth = false;
|
hasOpenParenth = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,89 +1,48 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CleanDateTimeParser
|
public static class CleanDateTimeParser
|
||||||
{
|
{
|
||||||
private readonly NamingOptions _options;
|
public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
|
||||||
|
|
||||||
public CleanDateTimeParser(NamingOptions options)
|
|
||||||
{
|
{
|
||||||
_options = options;
|
CleanDateTimeResult result = new CleanDateTimeResult(name);
|
||||||
}
|
var len = cleanDateTimeRegexes.Count;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
public CleanDateTimeResult Clean(string name)
|
|
||||||
{
|
|
||||||
var originalName = name;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(name) ?? string.Empty;
|
if (TryClean(name, cleanDateTimeRegexes[i], ref result))
|
||||||
// Check supported extensions
|
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
|
|
||||||
&& !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
// Dummy up a file extension because the expressions will fail without one
|
return result;
|
||||||
// This is tricky because we can't just check Path.GetExtension for empty
|
|
||||||
// If the input is "St. Vincent (2014)", it will produce ". Vincent (2014)" as the extension
|
|
||||||
name += ".mkv";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
|
return result;
|
||||||
.FirstOrDefault(i => i.HasChanged) ??
|
|
||||||
new CleanDateTimeResult { Name = originalName };
|
|
||||||
|
|
||||||
if (result.HasChanged)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a second pass, running clean string first
|
|
||||||
var cleanStringResult = new CleanStringParser().Clean(name, _options.CleanStringRegexes);
|
|
||||||
|
|
||||||
if (!cleanStringResult.HasChanged)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _options.CleanDateTimeRegexes.Select(i => Clean(cleanStringResult.Name, i))
|
|
||||||
.FirstOrDefault(i => i.HasChanged) ??
|
|
||||||
result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CleanDateTimeResult Clean(string name, Regex expression)
|
private static bool TryClean(string name, Regex expression, ref CleanDateTimeResult result)
|
||||||
{
|
{
|
||||||
var result = new CleanDateTimeResult();
|
|
||||||
|
|
||||||
var match = expression.Match(name);
|
var match = expression.Match(name);
|
||||||
|
|
||||||
if (match.Success
|
if (match.Success
|
||||||
&& match.Groups.Count == 4
|
&& match.Groups.Count == 5
|
||||||
&& match.Groups[1].Success
|
&& match.Groups[1].Success
|
||||||
&& match.Groups[2].Success
|
&& match.Groups[2].Success
|
||||||
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
|
||||||
{
|
{
|
||||||
name = match.Groups[1].Value;
|
result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
|
||||||
result.Year = year;
|
return true;
|
||||||
result.HasChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Name = name;
|
return false;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,33 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
public class CleanDateTimeResult
|
public readonly struct CleanDateTimeResult
|
||||||
{
|
{
|
||||||
|
public CleanDateTimeResult(string name, int? year)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Year = year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CleanDateTimeResult(string name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Year = null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the name.
|
/// Gets the name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name.</value>
|
/// <value>The name.</value>
|
||||||
public string Name { get; set; }
|
public string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the year.
|
/// Gets the year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The year.</value>
|
/// <value>The year.</value>
|
||||||
public int? Year { get; set; }
|
public int? Year { get; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance has changed.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
|
|
||||||
public bool HasChanged { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -9,44 +11,35 @@ namespace Emby.Naming.Video
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CleanStringParser
|
public static class CleanStringParser
|
||||||
{
|
{
|
||||||
public CleanStringResult Clean(string name, IEnumerable<Regex> expressions)
|
public static bool TryClean(string name, IReadOnlyList<Regex> expressions, out ReadOnlySpan<char> newName)
|
||||||
{
|
{
|
||||||
var hasChanged = false;
|
var len = expressions.Count;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
foreach (var exp in expressions)
|
|
||||||
{
|
{
|
||||||
var result = Clean(name, exp);
|
if (TryClean(name, expressions[i], out newName))
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(result.Name))
|
|
||||||
{
|
{
|
||||||
name = result.Name;
|
return true;
|
||||||
hasChanged = hasChanged || result.HasChanged;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CleanStringResult
|
newName = ReadOnlySpan<char>.Empty;
|
||||||
{
|
return false;
|
||||||
Name = name,
|
|
||||||
HasChanged = hasChanged
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CleanStringResult Clean(string name, Regex expression)
|
private static bool TryClean(string name, Regex expression, out ReadOnlySpan<char> newName)
|
||||||
{
|
{
|
||||||
var result = new CleanStringResult();
|
|
||||||
|
|
||||||
var match = expression.Match(name);
|
var match = expression.Match(name);
|
||||||
|
int index = match.Index;
|
||||||
if (match.Success)
|
if (match.Success && index != 0)
|
||||||
{
|
{
|
||||||
result.HasChanged = true;
|
newName = name.AsSpan().Slice(0, match.Index);
|
||||||
name = name.Substring(0, match.Index);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Name = name;
|
newName = string.Empty;
|
||||||
return result;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
|
||||||
{
|
|
||||||
public class CleanStringResult
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the name.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The name.</value>
|
|
||||||
public string Name { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets a value indicating whether this instance has changed.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance has changed; otherwise, <c>false</c>.</value>
|
|
||||||
public bool HasChanged { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ namespace Emby.Naming.Video
|
||||||
_options = options;
|
_options = options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StackResult 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
|
||||||
{
|
{
|
||||||
|
@ -29,7 +29,7 @@ namespace Emby.Naming.Video
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StackResult 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
|
||||||
{
|
{
|
||||||
|
@ -38,9 +38,8 @@ namespace Emby.Naming.Video
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public StackResult ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
public IEnumerable<FileStack> ResolveAudioBooks(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var result = new StackResult();
|
|
||||||
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
foreach (var directory in files.GroupBy(file => file.IsDirectory ? file.FullName : Path.GetDirectoryName(file.FullName)))
|
||||||
{
|
{
|
||||||
var stack = new FileStack()
|
var stack = new FileStack()
|
||||||
|
@ -58,20 +57,16 @@ namespace Emby.Naming.Video
|
||||||
stack.Files.Add(file.FullName);
|
stack.Files.Add(file.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Stacks.Add(stack);
|
yield return stack;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public StackResult Resolve(IEnumerable<FileSystemMetadata> files)
|
public IEnumerable<FileStack> Resolve(IEnumerable<FileSystemMetadata> files)
|
||||||
{
|
{
|
||||||
var result = new StackResult();
|
|
||||||
|
|
||||||
var resolver = new VideoResolver(_options);
|
var resolver = new VideoResolver(_options);
|
||||||
|
|
||||||
var list = files
|
var list = files
|
||||||
.Where(i => i.IsDirectory || (resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName)))
|
.Where(i => i.IsDirectory || resolver.IsVideoFile(i.FullName) || resolver.IsStubFile(i.FullName))
|
||||||
.OrderBy(i => i.FullName)
|
.OrderBy(i => i.FullName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
@ -191,14 +186,12 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
if (stack.Files.Count > 1)
|
if (stack.Files.Count > 1)
|
||||||
{
|
{
|
||||||
result.Stacks.Add(stack);
|
yield return stack;
|
||||||
i += stack.Files.Count - 1;
|
i += stack.Files.Count - 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetRegexInput(FileSystemMetadata file)
|
private string GetRegexInput(FileSystemMetadata file)
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
|
||||||
{
|
|
||||||
public class StackResult
|
|
||||||
{
|
|
||||||
public List<FileStack> Stacks { get; set; }
|
|
||||||
|
|
||||||
public StackResult()
|
|
||||||
{
|
|
||||||
Stacks = new List<FileStack>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -10,25 +11,22 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
public static class StubResolver
|
public static class StubResolver
|
||||||
{
|
{
|
||||||
public static StubResult ResolveFile(string path, NamingOptions options)
|
public static bool TryResolveFile(string path, NamingOptions options, out string? stubType)
|
||||||
{
|
{
|
||||||
|
stubType = default;
|
||||||
|
|
||||||
if (path == null)
|
if (path == null)
|
||||||
{
|
{
|
||||||
return default;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return default;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new StubResult()
|
|
||||||
{
|
|
||||||
IsStub = true
|
|
||||||
};
|
|
||||||
|
|
||||||
path = Path.GetFileNameWithoutExtension(path);
|
path = Path.GetFileNameWithoutExtension(path);
|
||||||
var token = Path.GetExtension(path).TrimStart('.');
|
var token = Path.GetExtension(path).TrimStart('.');
|
||||||
|
|
||||||
|
@ -36,12 +34,12 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.StubType = rule.StubType;
|
stubType = rule.StubType;
|
||||||
break;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ namespace Emby.Naming.Video
|
||||||
public string StubType { get; set; }
|
public string StubType { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the type.
|
/// Gets or sets a value indicating whether this instance is a directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The type.</value>
|
/// <value>The type.</value>
|
||||||
public bool IsDirectory { get; set; }
|
public bool IsDirectory { get; set; }
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
|
@ -10,11 +11,14 @@ namespace Emby.Naming.Video
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="VideoInfo" /> class.
|
/// Initializes a new instance of the <see cref="VideoInfo" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public VideoInfo()
|
/// <param name="name">The name.</param>
|
||||||
|
public VideoInfo(string name)
|
||||||
{
|
{
|
||||||
Files = new List<VideoFileInfo>();
|
Name = name;
|
||||||
Extras = new List<VideoFileInfo>();
|
|
||||||
AlternateVersions = new List<VideoFileInfo>();
|
Files = Array.Empty<VideoFileInfo>();
|
||||||
|
Extras = Array.Empty<VideoFileInfo>();
|
||||||
|
AlternateVersions = Array.Empty<VideoFileInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -33,18 +37,18 @@ namespace Emby.Naming.Video
|
||||||
/// Gets or sets the files.
|
/// Gets or sets the files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The files.</value>
|
/// <value>The files.</value>
|
||||||
public List<VideoFileInfo> Files { get; set; }
|
public IReadOnlyList<VideoFileInfo> Files { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the extras.
|
/// Gets or sets the extras.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The extras.</value>
|
/// <value>The extras.</value>
|
||||||
public List<VideoFileInfo> Extras { get; set; }
|
public IReadOnlyList<VideoFileInfo> Extras { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the alternate versions.
|
/// Gets or sets the alternate versions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The alternate versions.</value>
|
/// <value>The alternate versions.</value>
|
||||||
public List<VideoFileInfo> AlternateVersions { get; set; }
|
public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,20 +41,19 @@ namespace Emby.Naming.Video
|
||||||
});
|
});
|
||||||
|
|
||||||
var stackResult = new StackResolver(_options)
|
var stackResult = new StackResolver(_options)
|
||||||
.Resolve(nonExtras);
|
.Resolve(nonExtras).ToList();
|
||||||
|
|
||||||
var remainingFiles = videoInfos
|
var remainingFiles = videoInfos
|
||||||
.Where(i => !stackResult.Stacks.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
|
.Where(i => !stackResult.Any(s => s.ContainsFile(i.Path, i.IsDirectory)))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var list = new List<VideoInfo>();
|
var list = new List<VideoInfo>();
|
||||||
|
|
||||||
foreach (var stack in stackResult.Stacks)
|
foreach (var stack in stackResult)
|
||||||
{
|
{
|
||||||
var info = new VideoInfo
|
var info = new VideoInfo(stack.Name)
|
||||||
{
|
{
|
||||||
Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList(),
|
Files = stack.Files.Select(i => videoResolver.Resolve(i, stack.IsDirectoryStack)).ToList()
|
||||||
Name = stack.Name
|
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
@ -85,10 +84,9 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
foreach (var media in standaloneMedia)
|
foreach (var media in standaloneMedia)
|
||||||
{
|
{
|
||||||
var info = new VideoInfo
|
var info = new VideoInfo(media.Name)
|
||||||
{
|
{
|
||||||
Files = new List<VideoFileInfo> { media },
|
Files = new List<VideoFileInfo> { media }
|
||||||
Name = media.Name
|
|
||||||
};
|
};
|
||||||
|
|
||||||
info.Year = info.Files[0].Year;
|
info.Year = info.Files[0].Year;
|
||||||
|
@ -128,7 +126,8 @@ namespace Emby.Naming.Video
|
||||||
.Except(extras)
|
.Except(extras)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
info.Extras.AddRange(extras);
|
extras.AddRange(info.Extras);
|
||||||
|
info.Extras = extras;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +140,8 @@ namespace Emby.Naming.Video
|
||||||
.Except(extrasByFileName)
|
.Except(extrasByFileName)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
info.Extras.AddRange(extrasByFileName);
|
extrasByFileName.AddRange(info.Extras);
|
||||||
|
info.Extras = extrasByFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's only one video, accept all trailers
|
// If there's only one video, accept all trailers
|
||||||
|
@ -152,7 +152,8 @@ namespace Emby.Naming.Video
|
||||||
.Where(i => i.ExtraType == ExtraType.Trailer)
|
.Where(i => i.ExtraType == ExtraType.Trailer)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
list[0].Extras.AddRange(trailers);
|
trailers.AddRange(list[0].Extras);
|
||||||
|
list[0].Extras = trailers;
|
||||||
|
|
||||||
remainingFiles = remainingFiles
|
remainingFiles = remainingFiles
|
||||||
.Except(trailers)
|
.Except(trailers)
|
||||||
|
@ -160,14 +161,13 @@ namespace Emby.Naming.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whatever files are left, just add them
|
// Whatever files are left, just add them
|
||||||
list.AddRange(remainingFiles.Select(i => new VideoInfo
|
list.AddRange(remainingFiles.Select(i => new VideoInfo(i.Name)
|
||||||
{
|
{
|
||||||
Files = new List<VideoFileInfo> { i },
|
Files = new List<VideoFileInfo> { i },
|
||||||
Name = i.Name,
|
|
||||||
Year = i.Year
|
Year = i.Year
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return list.OrderBy(i => i.Name);
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
|
private IEnumerable<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos)
|
||||||
|
@ -191,9 +191,18 @@ namespace Emby.Naming.Video
|
||||||
|
|
||||||
list.Add(ordered[0]);
|
list.Add(ordered[0]);
|
||||||
|
|
||||||
list[0].AlternateVersions = ordered.Skip(1).Select(i => i.Files[0]).ToList();
|
var alternateVersionsLen = ordered.Count - 1;
|
||||||
|
var alternateVersions = new VideoFileInfo[alternateVersionsLen];
|
||||||
|
for (int i = 0; i < alternateVersionsLen; i++)
|
||||||
|
{
|
||||||
|
alternateVersions[i] = ordered[i + 1].Files[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
list[0].AlternateVersions = alternateVersions;
|
||||||
list[0].Name = folderName;
|
list[0].Name = folderName;
|
||||||
list[0].Extras.AddRange(ordered.Skip(1).SelectMany(i => i.Extras));
|
var extras = ordered.Skip(1).SelectMany(i => i.Extras).ToList();
|
||||||
|
extras.AddRange(list[0].Extras);
|
||||||
|
list[0].Extras = extras;
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -22,7 +23,7 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <returns>VideoFileInfo.</returns>
|
||||||
public VideoFileInfo ResolveDirectory(string path)
|
public VideoFileInfo? ResolveDirectory(string path)
|
||||||
{
|
{
|
||||||
return Resolve(path, true);
|
return Resolve(path, true);
|
||||||
}
|
}
|
||||||
|
@ -32,7 +33,7 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <returns>VideoFileInfo.</returns>
|
||||||
public VideoFileInfo ResolveFile(string path)
|
public VideoFileInfo? ResolveFile(string path)
|
||||||
{
|
{
|
||||||
return Resolve(path, false);
|
return Resolve(path, false);
|
||||||
}
|
}
|
||||||
|
@ -42,10 +43,10 @@ namespace Emby.Naming.Video
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
|
/// <param name="isDirectory">if set to <c>true</c> [is folder].</param>
|
||||||
/// <param name="parseName">Whether or not the name should be parsed for info</param>
|
/// <param name="parseName">Whether or not the name should be parsed for info.</param>
|
||||||
/// <returns>VideoFileInfo.</returns>
|
/// <returns>VideoFileInfo.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
|
/// <exception cref="ArgumentNullException"><c>path</c> is <c>null</c>.</exception>
|
||||||
public VideoFileInfo Resolve(string path, bool isDirectory, bool parseName = true)
|
public VideoFileInfo? Resolve(string path, bool isDirectory, bool parseName = true)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
@ -53,8 +54,8 @@ namespace Emby.Naming.Video
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isStub = false;
|
bool isStub = false;
|
||||||
string container = null;
|
string? container = null;
|
||||||
string stubType = null;
|
string? stubType = null;
|
||||||
|
|
||||||
if (!isDirectory)
|
if (!isDirectory)
|
||||||
{
|
{
|
||||||
|
@ -63,17 +64,13 @@ namespace Emby.Naming.Video
|
||||||
// Check supported extensions
|
// Check supported extensions
|
||||||
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var stubResult = StubResolver.ResolveFile(path, _options);
|
|
||||||
|
|
||||||
isStub = stubResult.IsStub;
|
|
||||||
|
|
||||||
// It's not supported. Check stub extensions
|
// It's not supported. Check stub extensions
|
||||||
if (!isStub)
|
if (!StubResolver.TryResolveFile(path, _options, out stubType))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
stubType = stubResult.StubType;
|
isStub = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
container = extension.TrimStart('.');
|
container = extension.TrimStart('.');
|
||||||
|
@ -94,9 +91,10 @@ namespace Emby.Naming.Video
|
||||||
{
|
{
|
||||||
var cleanDateTimeResult = CleanDateTime(name);
|
var cleanDateTimeResult = CleanDateTime(name);
|
||||||
|
|
||||||
if (extraResult.ExtraType == null)
|
if (extraResult.ExtraType == null
|
||||||
|
&& TryCleanString(cleanDateTimeResult.Name, out ReadOnlySpan<char> newName))
|
||||||
{
|
{
|
||||||
name = CleanString(cleanDateTimeResult.Name).Name;
|
name = newName.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
year = cleanDateTimeResult.Year;
|
year = cleanDateTimeResult.Year;
|
||||||
|
@ -130,14 +128,14 @@ namespace Emby.Naming.Video
|
||||||
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CleanStringResult CleanString(string name)
|
public bool TryCleanString(string name, out ReadOnlySpan<char> newName)
|
||||||
{
|
{
|
||||||
return new CleanStringParser().Clean(name, _options.CleanStringRegexes);
|
return CleanStringParser.TryClean(name, _options.CleanStringRegexes, out newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CleanDateTimeResult CleanDateTime(string name)
|
public CleanDateTimeResult CleanDateTime(string name)
|
||||||
{
|
{
|
||||||
return new CleanDateTimeParser(_options).Clean(name);
|
return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Notifications;
|
using MediaBrowser.Controller.Notifications;
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.Notifications;
|
using MediaBrowser.Model.Notifications;
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
</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" />
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Activity;
|
using MediaBrowser.Model.Activity;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
|
|
|
@ -599,7 +599,7 @@ namespace Emby.Server.Implementations
|
||||||
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
HttpsPort = ServerConfiguration.DefaultHttpsPort;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonSerializer = new JsonSerializer(FileSystemManager);
|
JsonSerializer = new JsonSerializer();
|
||||||
|
|
||||||
if (Plugins != null)
|
if (Plugins != null)
|
||||||
{
|
{
|
||||||
|
@ -1018,7 +1018,7 @@ namespace Emby.Server.Implementations
|
||||||
{
|
{
|
||||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
||||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
||||||
.Select(x => Assembly.LoadFrom(x))
|
.Select(Assembly.LoadFrom)
|
||||||
.SelectMany(x => x.ExportedTypes)
|
.SelectMany(x => x.ExportedTypes)
|
||||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
@ -1718,29 +1718,6 @@ namespace Emby.Server.Implementations
|
||||||
_plugins = list.ToArray();
|
_plugins = list.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This returns localhost in the case of no external dns, and the hostname if the
|
|
||||||
/// dns is prefixed with a valid Uri prefix.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="externalDns">The external dns prefix to get the hostname of.</param>
|
|
||||||
/// <returns>The hostname in <paramref name="externalDns"/>.</returns>
|
|
||||||
private static string GetHostnameFromExternalDns(string externalDns)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(externalDns))
|
|
||||||
{
|
|
||||||
return "localhost";
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new Uri(externalDns).Host;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return externalDns;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void LaunchUrl(string url)
|
public virtual void LaunchUrl(string url)
|
||||||
{
|
{
|
||||||
if (!CanLaunchWebBrowser)
|
if (!CanLaunchWebBrowser)
|
||||||
|
|
|
@ -35,14 +35,6 @@ namespace Emby.Server.Implementations.Channels
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetUserDistinctValue(User user)
|
|
||||||
{
|
|
||||||
var channels = user.Policy.EnabledChannels
|
|
||||||
.OrderBy(i => i);
|
|
||||||
|
|
||||||
return string.Join("|", channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CleanDatabase(CancellationToken cancellationToken)
|
private void CleanDatabase(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
|
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
|
||||||
|
@ -75,19 +67,23 @@ namespace Emby.Server.Implementations.Channels
|
||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
_libraryManager.DeleteItem(item, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
item,
|
||||||
DeleteFileLocation = false
|
new DeleteOptions
|
||||||
|
{
|
||||||
}, false);
|
DeleteFileLocation = false
|
||||||
|
},
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, delete the channel itself
|
// Finally, delete the channel itself
|
||||||
_libraryManager.DeleteItem(channel, new DeleteOptions
|
_libraryManager.DeleteItem(
|
||||||
{
|
channel,
|
||||||
DeleteFileLocation = false
|
new DeleteOptions
|
||||||
|
{
|
||||||
}, false);
|
DeleteFileLocation = false
|
||||||
|
},
|
||||||
|
false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,18 +28,28 @@ namespace Emby.Server.Implementations.Channels
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Name => "Refresh Channels";
|
public string Name => "Refresh Channels";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Description => "Refreshes internet channel information.";
|
public string Description => "Refreshes internet channel information.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public string Category => "Internet Channels";
|
public string Category => "Internet Channels";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0;
|
public bool IsHidden => ((ChannelManager)_channelManager).Channels.Length == 0;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsEnabled => true;
|
public bool IsEnabled => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public bool IsLogged => true;
|
public bool IsLogged => true;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string Key => "RefreshInternetChannels";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||||
{
|
{
|
||||||
var manager = (ChannelManager)_channelManager;
|
var manager = (ChannelManager)_channelManager;
|
||||||
|
@ -50,18 +60,18 @@ namespace Emby.Server.Implementations.Channels
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Creates the triggers that define when the task will run
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[]
|
||||||
|
{
|
||||||
|
|
||||||
// Every so often
|
// Every so often
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
new TaskTriggerInfo
|
||||||
|
{
|
||||||
|
Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Key => "RefreshInternetChannels";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
#pragma warning disable SA1600
|
#pragma warning disable SA1600
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Server.Implementations.Images;
|
using Emby.Server.Implementations.Images;
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Emby.Server.Implementations.AppBase;
|
using Emby.Server.Implementations.AppBase;
|
||||||
|
|
|
@ -243,7 +243,7 @@ namespace Emby.Server.Implementations.Devices
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
await stream.CopyToAsync(fs).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,11 +29,11 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.1" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.7.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
#pragma warning disable SA1600
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.LiveTv;
|
|
||||||
using MediaBrowser.Controller.Plugins;
|
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Model.LiveTv;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
|
||||||
{
|
|
||||||
public class AutomaticRestartEntryPoint : IServerEntryPoint
|
|
||||||
{
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
private readonly ITaskManager _iTaskManager;
|
|
||||||
private readonly ISessionManager _sessionManager;
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
private readonly ILiveTvManager _liveTvManager;
|
|
||||||
|
|
||||||
private Timer _timer;
|
|
||||||
|
|
||||||
public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager)
|
|
||||||
{
|
|
||||||
_appHost = appHost;
|
|
||||||
_logger = logger;
|
|
||||||
_iTaskManager = iTaskManager;
|
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_config = config;
|
|
||||||
_liveTvManager = liveTvManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task RunAsync()
|
|
||||||
{
|
|
||||||
if (_appHost.CanSelfRestart)
|
|
||||||
{
|
|
||||||
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _appHost_HasPendingRestartChanged(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
DisposeTimer();
|
|
||||||
|
|
||||||
if (_appHost.HasPendingRestart)
|
|
||||||
{
|
|
||||||
_timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(15), TimeSpan.FromMinutes(15));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void TimerCallback(object state)
|
|
||||||
{
|
|
||||||
if (_config.Configuration.EnableAutomaticRestart)
|
|
||||||
{
|
|
||||||
var isIdle = await IsIdle().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (isIdle)
|
|
||||||
{
|
|
||||||
DisposeTimer();
|
|
||||||
|
|
||||||
_logger.LogInformation("Automatically restarting the system because it is idle and a restart is required.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_appHost.Restart();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error restarting server");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> IsIdle()
|
|
||||||
{
|
|
||||||
if (_iTaskManager.ScheduledTasks.Any(i => i.State != TaskState.Idle))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_liveTvManager.Services.Count == 1)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var timers = await _liveTvManager.GetTimers(new TimerQuery(), CancellationToken.None).ConfigureAwait(false);
|
|
||||||
if (timers.Items.Any(i => i.Status == RecordingStatus.InProgress))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error getting timers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
|
|
||||||
return !_sessionManager.Sessions.Any(i => (now - i.LastActivityDate).TotalMinutes < 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
|
|
||||||
|
|
||||||
DisposeTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeTimer()
|
|
||||||
{
|
|
||||||
if (_timer != null)
|
|
||||||
{
|
|
||||||
_timer.Dispose();
|
|
||||||
_timer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,7 +16,6 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Udp;
|
using Emby.Server.Implementations.Udp;
|
||||||
|
|
|
@ -197,7 +197,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||||
if (File.Exists(responseCachePath)
|
if (File.Exists(responseCachePath)
|
||||||
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
&& _fileSystem.GetLastWriteTimeUtc(responseCachePath).Add(cacheLength) > DateTime.UtcNow)
|
||||||
{
|
{
|
||||||
var stream = _fileSystem.GetFileStream(responseCachePath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true);
|
var stream = new FileStream(responseCachePath, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||||
|
|
||||||
return new HttpResponseInfo
|
return new HttpResponseInfo
|
||||||
{
|
{
|
||||||
|
@ -220,7 +220,7 @@ namespace Emby.Server.Implementations.HttpClientManager
|
||||||
FileMode.Create,
|
FileMode.Create,
|
||||||
FileAccess.Write,
|
FileAccess.Write,
|
||||||
FileShare.None,
|
FileShare.None,
|
||||||
StreamDefaults.DefaultFileStreamBufferSize,
|
IODefaults.FileStreamBufferSize,
|
||||||
true))
|
true))
|
||||||
{
|
{
|
||||||
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
|
await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||||
|
|
|
@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
SetRangeValues();
|
SetRangeValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
FileShare = FileShareMode.Read;
|
FileShare = FileShare.Read;
|
||||||
Cookies = new List<Cookie>();
|
Cookies = new List<Cookie>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
public List<Cookie> Cookies { get; private set; }
|
public List<Cookie> Cookies { get; private set; }
|
||||||
|
|
||||||
public FileShareMode FileShare { get; set; }
|
public FileShare FileShare { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the options.
|
/// Gets the options.
|
||||||
|
@ -222,17 +222,17 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShareMode fileShareMode, CancellationToken cancellationToken)
|
public async Task TransmitFile(Stream stream, string path, long offset, long count, FileShare fileShare, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var fileOpenOptions = FileOpenOptions.SequentialScan;
|
var fileOptions = FileOptions.SequentialScan;
|
||||||
|
|
||||||
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
// use non-async filestream along with read due to https://github.com/dotnet/corefx/issues/6039
|
||||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
fileOpenOptions |= FileOpenOptions.Asynchronous;
|
fileOptions |= FileOptions.Asynchronous;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShareMode, fileOpenOptions))
|
using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, fileOptions))
|
||||||
{
|
{
|
||||||
if (offset > 0)
|
if (offset > 0)
|
||||||
{
|
{
|
||||||
|
@ -245,7 +245,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await fs.CopyToAsync(stream, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
await fs.CopyToAsync(stream, IODefaults.CopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -440,7 +440,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
||||||
public Task<object> GetStaticFileResult(IRequest requestContext,
|
public Task<object> GetStaticFileResult(IRequest requestContext,
|
||||||
string path,
|
string path,
|
||||||
FileShareMode fileShare = FileShareMode.Read)
|
FileShare fileShare = FileShare.Read)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
|
@ -464,7 +464,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
throw new ArgumentException("Path can't be empty.", nameof(options));
|
throw new ArgumentException("Path can't be empty.", nameof(options));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileShare != FileShareMode.Read && fileShare != FileShareMode.ReadWrite)
|
if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("FileShare must be either Read or ReadWrite");
|
throw new ArgumentException("FileShare must be either Read or ReadWrite");
|
||||||
}
|
}
|
||||||
|
@ -492,9 +492,9 @@ namespace Emby.Server.Implementations.HttpServer
|
||||||
/// <param name="path">The path.</param>
|
/// <param name="path">The path.</param>
|
||||||
/// <param name="fileShare">The file share.</param>
|
/// <param name="fileShare">The file share.</param>
|
||||||
/// <returns>Stream.</returns>
|
/// <returns>Stream.</returns>
|
||||||
private Stream GetFileStream(string path, FileShareMode fileShare)
|
private Stream GetFileStream(string path, FileShare fileShare)
|
||||||
{
|
{
|
||||||
return _fileSystem.GetFileStream(path, FileOpenMode.Open, FileAccessMode.Read, fileShare);
|
return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<object> GetStaticResult(IRequest requestContext,
|
public Task<object> GetStaticResult(IRequest requestContext,
|
||||||
|
|
|
@ -365,87 +365,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
return GetLastWriteTimeUtc(GetFileSystemInfo(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the file stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">The path.</param>
|
|
||||||
/// <param name="mode">The mode.</param>
|
|
||||||
/// <param name="access">The access.</param>
|
|
||||||
/// <param name="share">The share.</param>
|
|
||||||
/// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
|
|
||||||
/// <returns>FileStream.</returns>
|
|
||||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, bool isAsync = false)
|
|
||||||
{
|
|
||||||
if (isAsync)
|
|
||||||
{
|
|
||||||
return GetFileStream(path, mode, access, share, FileOpenOptions.Asynchronous);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetFileStream(path, mode, access, share, FileOpenOptions.None);
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual Stream GetFileStream(string path, FileOpenMode mode, FileAccessMode access, FileShareMode share, FileOpenOptions fileOpenOptions)
|
|
||||||
=> new FileStream(path, GetFileMode(mode), GetFileAccess(access), GetFileShare(share), 4096, GetFileOptions(fileOpenOptions));
|
|
||||||
|
|
||||||
private static FileOptions GetFileOptions(FileOpenOptions mode)
|
|
||||||
{
|
|
||||||
var val = (int)mode;
|
|
||||||
return (FileOptions)val;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FileMode GetFileMode(FileOpenMode mode)
|
|
||||||
{
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
//case FileOpenMode.Append:
|
|
||||||
// return FileMode.Append;
|
|
||||||
case FileOpenMode.Create:
|
|
||||||
return FileMode.Create;
|
|
||||||
case FileOpenMode.CreateNew:
|
|
||||||
return FileMode.CreateNew;
|
|
||||||
case FileOpenMode.Open:
|
|
||||||
return FileMode.Open;
|
|
||||||
case FileOpenMode.OpenOrCreate:
|
|
||||||
return FileMode.OpenOrCreate;
|
|
||||||
//case FileOpenMode.Truncate:
|
|
||||||
// return FileMode.Truncate;
|
|
||||||
default:
|
|
||||||
throw new Exception("Unrecognized FileOpenMode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FileAccess GetFileAccess(FileAccessMode mode)
|
|
||||||
{
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
//case FileAccessMode.ReadWrite:
|
|
||||||
// return FileAccess.ReadWrite;
|
|
||||||
case FileAccessMode.Write:
|
|
||||||
return FileAccess.Write;
|
|
||||||
case FileAccessMode.Read:
|
|
||||||
return FileAccess.Read;
|
|
||||||
default:
|
|
||||||
throw new Exception("Unrecognized FileAccessMode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static FileShare GetFileShare(FileShareMode mode)
|
|
||||||
{
|
|
||||||
switch (mode)
|
|
||||||
{
|
|
||||||
case FileShareMode.ReadWrite:
|
|
||||||
return FileShare.ReadWrite;
|
|
||||||
case FileShareMode.Write:
|
|
||||||
return FileShare.Write;
|
|
||||||
case FileShareMode.Read:
|
|
||||||
return FileShare.Read;
|
|
||||||
case FileShareMode.None:
|
|
||||||
return FileShare.None;
|
|
||||||
default:
|
|
||||||
throw new Exception("Unrecognized FileShareMode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void SetHidden(string path, bool isHidden)
|
public virtual void SetHidden(string path, bool isHidden)
|
||||||
{
|
{
|
||||||
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
if (OperatingSystem.Id != OperatingSystemId.Windows)
|
||||||
|
|
|
@ -36,7 +36,6 @@ using MediaBrowser.Controller.Sorting;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
@ -54,6 +53,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
|
private NamingOptions _namingOptions;
|
||||||
|
private string[] _videoFileExtensions;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the postscan tasks.
|
/// Gets or sets the postscan tasks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -708,10 +710,10 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the root media folder
|
/// Creates the root media folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>AggregateFolder.</returns>
|
/// <returns>AggregateFolder.</returns>
|
||||||
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded</exception>
|
/// <exception cref="InvalidOperationException">Cannot create the root folder until plugins have loaded.</exception>
|
||||||
public AggregateFolder CreateRootFolder()
|
public AggregateFolder CreateRootFolder()
|
||||||
{
|
{
|
||||||
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
|
var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
|
||||||
|
@ -822,7 +824,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
// If this returns multiple items it could be tricky figuring out which one is correct.
|
// If this returns multiple items it could be tricky figuring out which one is correct.
|
||||||
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
// In most cases, the newest one will be and the others obsolete but not yet cleaned up
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
|
@ -842,7 +843,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a Person
|
/// Gets the person.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <returns>Task{Person}.</returns>
|
/// <returns>Task{Person}.</returns>
|
||||||
|
@ -852,7 +853,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a Studio
|
/// Gets the studio.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <returns>Task{Studio}.</returns>
|
/// <returns>Task{Studio}.</returns>
|
||||||
|
@ -877,7 +878,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a Genre
|
/// Gets the genre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <returns>Task{Genre}.</returns>
|
/// <returns>Task{Genre}.</returns>
|
||||||
|
@ -887,7 +888,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the genre.
|
/// Gets the music genre.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <returns>Task{MusicGenre}.</returns>
|
/// <returns>Task{MusicGenre}.</returns>
|
||||||
|
@ -897,7 +898,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a Year
|
/// Gets the year.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="value">The value.</param>
|
/// <param name="value">The value.</param>
|
||||||
/// <returns>Task{Year}.</returns>
|
/// <returns>Task{Year}.</returns>
|
||||||
|
@ -1074,9 +1075,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
var innerProgress = new ActionableProgress<double>();
|
var innerProgress = new ActionableProgress<double>();
|
||||||
|
|
||||||
innerProgress.RegisterAction(pct => progress.Report(pct * .96));
|
innerProgress.RegisterAction(pct => progress.Report(pct * pct * 0.96));
|
||||||
|
|
||||||
// Now validate the entire media library
|
// Validate the entire media library
|
||||||
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
await RootFolder.ValidateChildren(innerProgress, cancellationToken, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true).ConfigureAwait(false);
|
||||||
|
|
||||||
progress.Report(96);
|
progress.Report(96);
|
||||||
|
@ -1085,7 +1086,6 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
|
innerProgress.RegisterAction(pct => progress.Report(96 + (pct * .04)));
|
||||||
|
|
||||||
// Run post-scan tasks
|
|
||||||
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
|
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
progress.Report(100);
|
progress.Report(100);
|
||||||
|
@ -1136,7 +1136,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error running postscan task");
|
_logger.LogError(ex, "Error running post-scan task");
|
||||||
}
|
}
|
||||||
|
|
||||||
numComplete++;
|
numComplete++;
|
||||||
|
@ -2382,7 +2382,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public int? GetSeasonNumberFromPath(string path)
|
public int? GetSeasonNumberFromPath(string path)
|
||||||
{
|
{
|
||||||
return new SeasonPathParser().Parse(path, true, true).SeasonNumber;
|
return SeasonPathParser.Parse(path, true, true).SeasonNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
public bool FillMissingEpisodeNumbersFromPath(Episode episode, bool forceRefresh)
|
||||||
|
@ -2508,21 +2508,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
public NamingOptions GetNamingOptions()
|
public NamingOptions GetNamingOptions()
|
||||||
{
|
|
||||||
return GetNamingOptionsInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private NamingOptions _namingOptions;
|
|
||||||
private string[] _videoFileExtensions;
|
|
||||||
|
|
||||||
private NamingOptions GetNamingOptionsInternal()
|
|
||||||
{
|
{
|
||||||
if (_namingOptions == null)
|
if (_namingOptions == null)
|
||||||
{
|
{
|
||||||
var options = new NamingOptions();
|
_namingOptions = new NamingOptions();
|
||||||
|
_videoFileExtensions = _namingOptions.VideoFileExtensions;
|
||||||
_namingOptions = options;
|
|
||||||
_videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _namingOptions;
|
return _namingOptions;
|
||||||
|
@ -2533,11 +2523,10 @@ namespace Emby.Server.Implementations.Library
|
||||||
var resolver = new VideoResolver(GetNamingOptions());
|
var resolver = new VideoResolver(GetNamingOptions());
|
||||||
|
|
||||||
var result = resolver.CleanDateTime(name);
|
var result = resolver.CleanDateTime(name);
|
||||||
var cleanName = resolver.CleanString(result.Name);
|
|
||||||
|
|
||||||
return new ItemLookupInfo
|
return new ItemLookupInfo
|
||||||
{
|
{
|
||||||
Name = cleanName.Name,
|
Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
|
||||||
Year = result.Year
|
Year = result.Year
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,7 +76,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determine if the supplied file data points to a music album
|
/// Determine if the supplied file data points to a music album.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
|
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
|
||||||
{
|
{
|
||||||
|
@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determine if the supplied resolve args should be considered a music album
|
/// Determine if the supplied resolve args should be considered a music album.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">The args.</param>
|
/// <param name="args">The args.</param>
|
||||||
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
|
||||||
|
@ -104,7 +104,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determine if the supplied list contains what we should consider music
|
/// Determine if the supplied list contains what we should consider music.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private bool ContainsMusic(
|
private bool ContainsMusic(
|
||||||
IEnumerable<FileSystemMetadata> list,
|
IEnumerable<FileSystemMetadata> list,
|
||||||
|
@ -118,6 +118,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
var discSubfolderCount = 0;
|
var discSubfolderCount = 0;
|
||||||
var notMultiDisc = false;
|
var notMultiDisc = false;
|
||||||
|
|
||||||
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||||
|
var parser = new AlbumParser(namingOptions);
|
||||||
foreach (var fileSystemInfo in list)
|
foreach (var fileSystemInfo in list)
|
||||||
{
|
{
|
||||||
if (fileSystemInfo.IsDirectory)
|
if (fileSystemInfo.IsDirectory)
|
||||||
|
@ -134,7 +136,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
if (hasMusic)
|
if (hasMusic)
|
||||||
{
|
{
|
||||||
if (IsMultiDiscFolder(path, libraryOptions))
|
if (parser.IsMultiPart(path))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Found multi-disc folder: " + path);
|
logger.LogDebug("Found multi-disc folder: " + path);
|
||||||
discSubfolderCount++;
|
discSubfolderCount++;
|
||||||
|
@ -165,15 +167,5 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
return discSubfolderCount > 0;
|
return discSubfolderCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
|
|
||||||
{
|
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
|
||||||
|
|
||||||
var parser = new AlbumParser(namingOptions);
|
|
||||||
var result = parser.ParseMultiPart(path);
|
|
||||||
|
|
||||||
return result.IsMultiPart;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,28 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
|
||||||
{
|
{
|
||||||
|
private string[] _validCollectionTypes = new[]
|
||||||
|
{
|
||||||
|
CollectionType.Movies,
|
||||||
|
CollectionType.HomeVideos,
|
||||||
|
CollectionType.MusicVideos,
|
||||||
|
CollectionType.Movies,
|
||||||
|
CollectionType.Photos
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
|
/// <param name="imageProcessor">The image processor.</param>
|
||||||
|
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
|
||||||
|
: base(libraryManager)
|
||||||
|
{
|
||||||
|
_imageProcessor = imageProcessor;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the priority.
|
/// Gets the priority.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -144,7 +166,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
|
|
||||||
foreach (var video in resolverResult)
|
foreach (var video in resolverResult)
|
||||||
{
|
{
|
||||||
var firstVideo = video.Files.First();
|
var firstVideo = video.Files[0];
|
||||||
|
|
||||||
var videoItem = new T
|
var videoItem = new T
|
||||||
{
|
{
|
||||||
|
@ -230,7 +252,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
// Owned items will be caught by the plain video resolver
|
// Owned items will be caught by the plain video resolver
|
||||||
if (args.Parent == null)
|
if (args.Parent == null)
|
||||||
{
|
{
|
||||||
//return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
// return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +297,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
{
|
{
|
||||||
item = ResolveVideo<Movie>(args, true);
|
item = ResolveVideo<Movie>(args, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
||||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
@ -319,7 +340,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
{
|
{
|
||||||
if (item is Movie || item is MusicVideo)
|
if (item is Movie || item is MusicVideo)
|
||||||
{
|
{
|
||||||
//we need to only look at the name of this actual item (not parents)
|
// We need to only look at the name of this actual item (not parents)
|
||||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
|
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(justName))
|
if (!string.IsNullOrEmpty(justName))
|
||||||
|
@ -347,9 +368,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds a movie based on a child file system entries
|
/// Finds a movie based on a child file system entries.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <returns>Movie.</returns>
|
/// <returns>Movie.</returns>
|
||||||
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
||||||
where T : Video, new()
|
where T : Video, new()
|
||||||
|
@ -377,6 +397,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
Set3DFormat(movie);
|
Set3DFormat(movie);
|
||||||
return movie;
|
return movie;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsBluRayDirectory(child.FullName, filename, directoryService))
|
if (IsBluRayDirectory(child.FullName, filename, directoryService))
|
||||||
{
|
{
|
||||||
var movie = new T
|
var movie = new T
|
||||||
|
@ -407,9 +428,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Allow GetMultiDiscMovie in here
|
// TODO: Allow GetMultiDiscMovie in here
|
||||||
const bool supportsMultiVersion = true;
|
const bool SupportsMultiVersion = true;
|
||||||
|
|
||||||
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion, collectionType, parseName) ??
|
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, SupportsMultiVersion, collectionType, parseName) ??
|
||||||
new MultiItemResolverResult();
|
new MultiItemResolverResult();
|
||||||
|
|
||||||
if (result.Items.Count == 1)
|
if (result.Items.Count == 1)
|
||||||
|
@ -437,7 +458,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the multi disc movie.
|
/// Gets the multi disc movie.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="multiDiscFolders">The folders.</param>
|
/// <param name="multiDiscFolders">The folders.</param>
|
||||||
/// <param name="directoryService">The directory service.</param>
|
/// <param name="directoryService">The directory service.</param>
|
||||||
/// <returns>``0.</returns>
|
/// <returns>``0.</returns>
|
||||||
|
@ -451,7 +471,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
var subFileEntries = directoryService.GetFileSystemEntries(i);
|
var subFileEntries = directoryService.GetFileSystemEntries(i);
|
||||||
|
|
||||||
var subfolders = subFileEntries
|
var subfolders = subFileEntries
|
||||||
.Where(e => e.IsDirectory)
|
.Where(e => e.IsDirectory)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
|
if (subfolders.Any(s => IsDvdDirectory(s.FullName, s.Name, directoryService)))
|
||||||
|
@ -459,6 +479,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
videoTypes.Add(VideoType.Dvd);
|
videoTypes.Add(VideoType.Dvd);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
|
if (subfolders.Any(s => IsBluRayDirectory(s.FullName, s.Name, directoryService)))
|
||||||
{
|
{
|
||||||
videoTypes.Add(VideoType.BluRay);
|
videoTypes.Add(VideoType.BluRay);
|
||||||
|
@ -476,7 +497,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
}).OrderBy(i => i).ToList();
|
}).OrderBy(i => i).ToList();
|
||||||
|
|
||||||
// If different video types were found, don't allow this
|
// If different video types were found, don't allow this
|
||||||
|
@ -491,11 +511,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
}
|
}
|
||||||
|
|
||||||
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
|
||||||
var resolver = new StackResolver(namingOptions);
|
|
||||||
|
|
||||||
var result = resolver.ResolveDirectories(folderPaths);
|
var result = new StackResolver(namingOptions).ResolveDirectories(folderPaths).ToList();
|
||||||
|
|
||||||
if (result.Stacks.Count != 1)
|
if (result.Count != 1)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -508,7 +527,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
|
|
||||||
VideoType = videoTypes[0],
|
VideoType = videoTypes[0],
|
||||||
|
|
||||||
Name = result.Stacks[0].Name
|
Name = result[0].Name
|
||||||
};
|
};
|
||||||
|
|
||||||
SetIsoType(returnVideo);
|
SetIsoType(returnVideo);
|
||||||
|
@ -516,15 +535,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return returnVideo;
|
return returnVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] ValidCollectionTypes = new[]
|
|
||||||
{
|
|
||||||
CollectionType.Movies,
|
|
||||||
CollectionType.HomeVideos,
|
|
||||||
CollectionType.MusicVideos,
|
|
||||||
CollectionType.Movies,
|
|
||||||
CollectionType.Photos
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool IsInvalid(Folder parent, string collectionType)
|
private bool IsInvalid(Folder parent, string collectionType)
|
||||||
{
|
{
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
|
@ -540,20 +550,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !ValidCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
|
return !_validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
|
||||||
|
|
||||||
private IImageProcessor _imageProcessor;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="MovieResolver"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="libraryManager">The library manager.</param>
|
|
||||||
/// <param name="imageProcessor">The image processor.</param>
|
|
||||||
public MovieResolver(ILibraryManager libraryManager, IImageProcessor imageProcessor)
|
|
||||||
: base(libraryManager)
|
|
||||||
{
|
|
||||||
_imageProcessor = imageProcessor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,12 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.TV
|
namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class SeasonResolver
|
/// Class SeasonResolver.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SeasonResolver : FolderResolver<Season>
|
public class SeasonResolver : FolderResolver<Season>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The _config
|
|
||||||
/// </summary>
|
|
||||||
private readonly IServerConfigurationManager _config;
|
private readonly IServerConfigurationManager _config;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
|
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
@ -45,14 +40,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
/// <returns>Season.</returns>
|
/// <returns>Season.</returns>
|
||||||
protected override Season Resolve(ItemResolveArgs args)
|
protected override Season Resolve(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
if (args.Parent is Series && args.IsDirectory)
|
if (args.Parent is Series series && args.IsDirectory)
|
||||||
{
|
{
|
||||||
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
|
||||||
var series = ((Series)args.Parent);
|
|
||||||
|
|
||||||
var path = args.Path;
|
var path = args.Path;
|
||||||
|
|
||||||
var seasonParserResult = new SeasonPathParser().Parse(path, true, true);
|
var seasonParserResult = SeasonPathParser.Parse(path, true, true);
|
||||||
|
|
||||||
var season = new Season
|
var season = new Season
|
||||||
{
|
{
|
||||||
|
@ -74,7 +68,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
{
|
{
|
||||||
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
|
if (episodeInfo.EpisodeNumber.HasValue && episodeInfo.SeasonNumber.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
|
_logger.LogDebug(
|
||||||
|
"Found folder underneath series with episode number: {0}. Season {1}. Episode {2}",
|
||||||
path,
|
path,
|
||||||
episodeInfo.SeasonNumber.Value,
|
episodeInfo.SeasonNumber.Value,
|
||||||
episodeInfo.EpisodeNumber.Value);
|
episodeInfo.EpisodeNumber.Value);
|
||||||
|
@ -90,7 +85,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
|
|
||||||
season.Name = seasonNumber == 0 ?
|
season.Name = seasonNumber == 0 ?
|
||||||
args.LibraryOptions.SeasonZeroDisplayName :
|
args.LibraryOptions.SeasonZeroDisplayName :
|
||||||
string.Format(_localization.GetLocalizedString("NameSeasonNumber"), seasonNumber.ToString(UsCulture), args.GetLibraryOptions().PreferredMetadataLanguage);
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
_localization.GetLocalizedString("NameSeasonNumber"),
|
||||||
|
seasonNumber,
|
||||||
|
args.GetLibraryOptions().PreferredMetadataLanguage);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -203,7 +203,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
|
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
|
||||||
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
|
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
|
||||||
{
|
{
|
||||||
var seasonNumber = new SeasonPathParser().Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
var seasonNumber = SeasonPathParser.Parse(path, isTvContentType, isTvContentType).SeasonNumber;
|
||||||
|
|
||||||
return seasonNumber.HasValue;
|
return seasonNumber.HasValue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,10 +291,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
&& authenticationProvider != null
|
&& authenticationProvider != null
|
||||||
&& !(authenticationProvider is DefaultAuthenticationProvider))
|
&& !(authenticationProvider is DefaultAuthenticationProvider))
|
||||||
{
|
{
|
||||||
// We should trust the user that the authprovider says, not what was typed
|
// Trust the username returned by the authentication provider
|
||||||
username = updatedUsername;
|
username = updatedUsername;
|
||||||
|
|
||||||
// Search the database for the user again; the authprovider might have created it
|
// Search the database for the user again
|
||||||
|
// the authentication provider might have created it
|
||||||
user = Users
|
user = Users
|
||||||
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(i => string.Equals(username, i.Name, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
@ -667,7 +668,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
throw new ArgumentException("Invalid username", nameof(newName));
|
throw new ArgumentException("Invalid username", nameof(newName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Name.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
if (user.Name.Equals(newName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The new and old names must be different.");
|
throw new ArgumentException("The new and old names must be different.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,14 +15,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IStreamHelper _streamHelper;
|
private readonly IStreamHelper _streamHelper;
|
||||||
|
|
||||||
public DirectRecorder(ILogger logger, IHttpClient httpClient, IFileSystem fileSystem, IStreamHelper streamHelper)
|
public DirectRecorder(ILogger logger, IHttpClient httpClient, IStreamHelper streamHelper)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_streamHelper = streamHelper;
|
_streamHelper = streamHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
|
@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||||
|
|
||||||
using (var output = _fileSystem.GetFileStream(targetFile, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
onStarted();
|
onStarted();
|
||||||
|
|
||||||
|
|
|
@ -427,7 +427,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
foreach (NameValuePair mapping in mappings)
|
foreach (NameValuePair mapping in mappings)
|
||||||
{
|
{
|
||||||
if (StringHelper.EqualsIgnoreCase(mapping.Name, channelId))
|
if (string.Equals(mapping.Name, channelId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return mapping.Value;
|
return mapping.Value;
|
||||||
}
|
}
|
||||||
|
@ -1664,10 +1664,10 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
{
|
{
|
||||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||||
{
|
{
|
||||||
return new EncodedRecorder(_logger, _fileSystem, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DirectRecorder(_logger, _httpClient, _fileSystem, _streamHelper);
|
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
private void OnSuccessfulRecording(TimerInfo timer, string path)
|
||||||
|
@ -1888,7 +1888,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
@ -1952,7 +1952,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = _fileSystem.GetFileStream(nfoPath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -14,7 +13,6 @@ using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Diagnostics;
|
using MediaBrowser.Model.Diagnostics;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
@ -24,7 +22,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
public class EncodedRecorder : IRecorder
|
public class EncodedRecorder : IRecorder
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private bool _hasExited;
|
private bool _hasExited;
|
||||||
|
@ -38,7 +35,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
public EncodedRecorder(
|
public EncodedRecorder(
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
IFileSystem fileSystem,
|
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
IJsonSerializer json,
|
IJsonSerializer json,
|
||||||
|
@ -46,7 +42,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
IServerConfigurationManager config)
|
IServerConfigurationManager config)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
|
||||||
_mediaEncoder = mediaEncoder;
|
_mediaEncoder = mediaEncoder;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_json = json;
|
_json = json;
|
||||||
|
@ -107,7 +102,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
|
||||||
|
|
||||||
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
|
||||||
_logFileStream = _fileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
|
_logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||||
|
|
||||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||||
|
|
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
FileMode.Open,
|
FileMode.Open,
|
||||||
FileAccess.Read,
|
FileAccess.Read,
|
||||||
FileShare.ReadWrite,
|
FileShare.ReadWrite,
|
||||||
StreamDefaults.DefaultFileStreamBufferSize,
|
IODefaults.FileStreamBufferSize,
|
||||||
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
|
allowAsyncFileRead ? FileOptions.SequentialScan | FileOptions.Asynchronous : FileOptions.SequentialScan);
|
||||||
|
|
||||||
public Task DeleteTempFiles()
|
public Task DeleteTempFiles()
|
||||||
|
@ -199,7 +199,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
await StreamHelper.CopyToAsync(
|
await StreamHelper.CopyToAsync(
|
||||||
inputStream,
|
inputStream,
|
||||||
stream,
|
stream,
|
||||||
StreamDefaults.DefaultCopyToBufferSize,
|
IODefaults.CopyToBufferSize,
|
||||||
emptyReadLimit,
|
emptyReadLimit,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.LiveTv;
|
using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Extensions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
|
@ -127,12 +127,12 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
|
Logger.LogInformation("Beginning {0} stream to {1}", GetType().Name, TempFilePath);
|
||||||
using (response)
|
using (response)
|
||||||
using (var stream = response.Content)
|
using (var stream = response.Content)
|
||||||
using (var fileStream = FileSystem.GetFileStream(TempFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, FileOpenOptions.None))
|
using (var fileStream = new FileStream(TempFilePath, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
await StreamHelper.CopyToAsync(
|
await StreamHelper.CopyToAsync(
|
||||||
stream,
|
stream,
|
||||||
fileStream,
|
fileStream,
|
||||||
StreamDefaults.DefaultCopyToBufferSize,
|
IODefaults.CopyToBufferSize,
|
||||||
() => Resolve(openTaskCompletionSource),
|
() => Resolve(openTaskCompletionSource),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"Channels": "القنوات",
|
"Channels": "القنوات",
|
||||||
"ChapterNameValue": "الباب {0}",
|
"ChapterNameValue": "الباب {0}",
|
||||||
"Collections": "مجموعات",
|
"Collections": "مجموعات",
|
||||||
"DeviceOfflineWithName": "تم قطع الاتصال بـ{0}",
|
"DeviceOfflineWithName": "تم قطع اتصال {0}",
|
||||||
"DeviceOnlineWithName": "{0} متصل",
|
"DeviceOnlineWithName": "{0} متصل",
|
||||||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||||
"Favorites": "التفضيلات",
|
"Favorites": "التفضيلات",
|
||||||
|
@ -75,8 +75,8 @@
|
||||||
"Songs": "الأغاني",
|
"Songs": "الأغاني",
|
||||||
"StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.",
|
"StartupEmbyServerIsLoading": "سيرفر Jellyfin قيد التشغيل . الرجاء المحاولة بعد قليل.",
|
||||||
"SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}",
|
"SubtitleDownloadFailureForItem": "عملية إنزال الترجمة فشلت لـ{0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} لـ {1}",
|
"SubtitleDownloadFailureFromForItem": "الترجمات فشلت في التحميل من {0} الى {1}",
|
||||||
"SubtitlesDownloadedForItem": "تم تحميل الترجمات لـ {0}",
|
"SubtitlesDownloadedForItem": "تم تحميل الترجمات الى {0}",
|
||||||
"Sync": "مزامنة",
|
"Sync": "مزامنة",
|
||||||
"System": "النظام",
|
"System": "النظام",
|
||||||
"TvShows": "البرامج التلفزيونية",
|
"TvShows": "البرامج التلفزيونية",
|
||||||
|
@ -88,7 +88,7 @@
|
||||||
"UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
|
"UserOfflineFromDevice": "تم قطع اتصال {0} من {1}",
|
||||||
"UserOnlineFromDevice": "{0} متصل عبر {1}",
|
"UserOnlineFromDevice": "{0} متصل عبر {1}",
|
||||||
"UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
|
"UserPasswordChangedWithName": "تم تغيير كلمة السر للمستخدم {0}",
|
||||||
"UserPolicyUpdatedWithName": "سياسة المستخدمين تم تحديثها لـ {0}",
|
"UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
|
||||||
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
|
"UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
|
||||||
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
|
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} تم اضافتها الى مكتبة الوسائط",
|
"ValueHasBeenAddedToLibrary": "{0} تم اضافتها الى مكتبة الوسائط",
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
|
||||||
"Application": "Aplicació",
|
"Application": "Aplicació",
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
|
"AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
|
||||||
"Books": "Llibres",
|
"Books": "Llibres",
|
||||||
"CameraImageUploadedFrom": "Una nova imatge de càmera ha sigut pujada des de {0}",
|
"CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
|
||||||
"Channels": "Canals",
|
"Channels": "Canals",
|
||||||
"ChapterNameValue": "Episodi {0}",
|
"ChapterNameValue": "Episodi {0}",
|
||||||
"Collections": "Col·leccions",
|
"Collections": "Col·leccions",
|
||||||
|
|
95
Emby.Server.Implementations/Localization/Core/fil.json
Normal file
95
Emby.Server.Implementations/Localization/Core/fil.json
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
{
|
||||||
|
"VersionNumber": "Bersyon {0}",
|
||||||
|
"ValueSpecialEpisodeName": "Espesyal - {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong media library",
|
||||||
|
"UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
|
||||||
|
"UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}",
|
||||||
|
"UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}",
|
||||||
|
"UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
|
||||||
|
"UserOnlineFromDevice": "Si {0} ay nakakonekta galing sa {1}",
|
||||||
|
"UserOfflineFromDevice": "Si {0} ay nadiskonekta galing sa {1}",
|
||||||
|
"UserLockedOutWithName": "Si {0} ay nalock out",
|
||||||
|
"UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
|
||||||
|
"UserDeletedWithName": "Natanggal na is user {0}",
|
||||||
|
"UserCreatedWithName": "Nagawa na si user {0}",
|
||||||
|
"User": "User",
|
||||||
|
"TvShows": "Pelikula",
|
||||||
|
"System": "Sistema",
|
||||||
|
"Sync": "Pag-sync",
|
||||||
|
"SubtitlesDownloadedForItem": "Naidownload na ang subtitles {0}",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}",
|
||||||
|
"StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.",
|
||||||
|
"Songs": "Kanta",
|
||||||
|
"Shows": "Pelikula",
|
||||||
|
"ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
|
||||||
|
"ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
|
||||||
|
"ScheduledTaskFailedWithName": "Hindi gumana and {0}",
|
||||||
|
"ProviderValue": "Ang provider ay {0}",
|
||||||
|
"PluginUpdatedWithName": "Naiupdate na ang {0}",
|
||||||
|
"PluginUninstalledWithName": "Naiuninstall na ang {0}",
|
||||||
|
"PluginInstalledWithName": "Nainstall na ang {0}",
|
||||||
|
"Plugin": "Plugin",
|
||||||
|
"Playlists": "Playlists",
|
||||||
|
"Photos": "Larawan",
|
||||||
|
"NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
|
||||||
|
"NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
|
||||||
|
"NotificationOptionUserLockedOut": "Nakalock out ang user",
|
||||||
|
"NotificationOptionTaskFailed": "Hindi gumana ang scheduled task",
|
||||||
|
"NotificationOptionServerRestartRequired": "Kailangan irestart ang server",
|
||||||
|
"NotificationOptionPluginUpdateInstalled": "Naiupdate na ang plugin",
|
||||||
|
"NotificationOptionPluginUninstalled": "Naiuninstall na ang plugin",
|
||||||
|
"NotificationOptionPluginInstalled": "Nainstall na ang plugin",
|
||||||
|
"NotificationOptionPluginError": "Hindi gumagana ang plugin",
|
||||||
|
"NotificationOptionNewLibraryContent": "May bagong content na naidagdag",
|
||||||
|
"NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti",
|
||||||
|
"NotificationOptionCameraImageUploaded": "Naiupload na ang picture",
|
||||||
|
"NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog",
|
||||||
|
"NotificationOptionAudioPlayback": "Nagsimula na ang patugtog",
|
||||||
|
"NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon",
|
||||||
|
"NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon",
|
||||||
|
"NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede idownload.",
|
||||||
|
"NameSeasonUnknown": "Hindi alam ang season",
|
||||||
|
"NameSeasonNumber": "Season {0}",
|
||||||
|
"NameInstallFailed": "Hindi nainstall ang {0}",
|
||||||
|
"MusicVideos": "Music video",
|
||||||
|
"Music": "Kanta",
|
||||||
|
"Movies": "Pelikula",
|
||||||
|
"MixedContent": "Halo-halong content",
|
||||||
|
"MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
|
||||||
|
"MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
|
||||||
|
"MessageApplicationUpdatedTo": "Ang Jellyfin Server ay naiupdate to {0}",
|
||||||
|
"MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
|
||||||
|
"Latest": "Pinakabago",
|
||||||
|
"LabelRunningTimeValue": "Oras: {0}",
|
||||||
|
"LabelIpAddressValue": "Ang IP Address ay {0}",
|
||||||
|
"ItemRemovedWithName": "Naitanggal ang {0} sa library",
|
||||||
|
"ItemAddedWithName": "Naidagdag ang {0} sa library",
|
||||||
|
"Inherit": "Manahin",
|
||||||
|
"HeaderRecordingGroups": "Pagtatalang Grupo",
|
||||||
|
"HeaderNextUp": "Susunod",
|
||||||
|
"HeaderLiveTV": "Live TV",
|
||||||
|
"HeaderFavoriteSongs": "Paboritong Kanta",
|
||||||
|
"HeaderFavoriteShows": "Paboritong Pelikula",
|
||||||
|
"HeaderFavoriteEpisodes": "Paboritong Episodes",
|
||||||
|
"HeaderFavoriteArtists": "Paboritong Artista",
|
||||||
|
"HeaderFavoriteAlbums": "Paboritong Albums",
|
||||||
|
"HeaderContinueWatching": "Ituloy Manood",
|
||||||
|
"HeaderCameraUploads": "Camera Uploads",
|
||||||
|
"HeaderAlbumArtists": "Artista ng Album",
|
||||||
|
"Genres": "Kategorya",
|
||||||
|
"Folders": "Folders",
|
||||||
|
"Favorites": "Paborito",
|
||||||
|
"FailedLoginAttemptWithUserName": "maling login galing {0}",
|
||||||
|
"DeviceOnlineWithName": "nakakonekta si {0}",
|
||||||
|
"DeviceOfflineWithName": "nadiskonekta si {0}",
|
||||||
|
"Collections": "Koleksyon",
|
||||||
|
"ChapterNameValue": "Kabanata {0}",
|
||||||
|
"Channels": "Channel",
|
||||||
|
"CameraImageUploadedFrom": "May bagong larawan na naupload galing {0}",
|
||||||
|
"Books": "Libro",
|
||||||
|
"AuthenticationSucceededWithUserName": "{0} na patunayan",
|
||||||
|
"Artists": "Artista",
|
||||||
|
"Application": "Aplikasyon",
|
||||||
|
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||||
|
"Albums": "Albums"
|
||||||
|
}
|
1
Emby.Server.Implementations/Localization/Core/gl.json
Normal file
1
Emby.Server.Implementations/Localization/Core/gl.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -6,7 +6,7 @@
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server sudah diperbarui ke {0}",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
|
"MessageApplicationUpdated": "Jellyfin Server sudah diperbarui",
|
||||||
"Latest": "Terbaru",
|
"Latest": "Terbaru",
|
||||||
"LabelIpAddressValue": "IP address: {0}",
|
"LabelIpAddressValue": "Alamat IP: {0}",
|
||||||
"ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
|
"ItemRemovedWithName": "{0} sudah dikeluarkan dari perpustakaan",
|
||||||
"ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
|
"ItemAddedWithName": "{0} sudah dimasukkan ke dalam perpustakaan",
|
||||||
"Inherit": "Warisan",
|
"Inherit": "Warisan",
|
||||||
|
@ -28,5 +28,63 @@
|
||||||
"Collections": "Koleksi",
|
"Collections": "Koleksi",
|
||||||
"Books": "Buku",
|
"Books": "Buku",
|
||||||
"Artists": "Artis",
|
"Artists": "Artis",
|
||||||
"Application": "Aplikasi"
|
"Application": "Aplikasi",
|
||||||
|
"ChapterNameValue": "Bagian {0}",
|
||||||
|
"Channels": "Saluran",
|
||||||
|
"TvShows": "Seri TV",
|
||||||
|
"SubtitleDownloadFailureFromForItem": "Talop gagal diunduh dari {0} untuk {1}",
|
||||||
|
"StartupEmbyServerIsLoading": "Peladen Jellyfin sedang dimuat. Silakan coba kembali beberapa saat lagi.",
|
||||||
|
"Songs": "Lagu",
|
||||||
|
"Playlists": "Daftar putar",
|
||||||
|
"NotificationOptionPluginUninstalled": "Plugin dilepas",
|
||||||
|
"MusicVideos": "Video musik",
|
||||||
|
"VersionNumber": "Versi {0}",
|
||||||
|
"ValueSpecialEpisodeName": "Spesial - {0}",
|
||||||
|
"ValueHasBeenAddedToLibrary": "{0} telah ditambahkan ke pustaka media Anda",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} telah selesai memutar {1} pada {2}",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} sedang memutar {1} pada {2}",
|
||||||
|
"UserPolicyUpdatedWithName": "Kebijakan pengguna telah diperbarui untuk {0}",
|
||||||
|
"UserPasswordChangedWithName": "Kata sandi telah diubah untuk pengguna {0}",
|
||||||
|
"UserOnlineFromDevice": "{0} sedang daring dari {1}",
|
||||||
|
"UserOfflineFromDevice": "{0} telah terputus dari {1}",
|
||||||
|
"UserLockedOutWithName": "Pengguna {0} telah dikunci",
|
||||||
|
"UserDownloadingItemWithValues": "{0} sedang mengunduh {1}",
|
||||||
|
"UserDeletedWithName": "Pengguna {0} telah dihapus",
|
||||||
|
"UserCreatedWithName": "Pengguna {0} telah dibuat",
|
||||||
|
"User": "Pengguna",
|
||||||
|
"System": "Sistem",
|
||||||
|
"Sync": "Sinkron",
|
||||||
|
"SubtitlesDownloadedForItem": "Talop telah diunduh untuk {0}",
|
||||||
|
"Shows": "Tayangan",
|
||||||
|
"ServerNameNeedsToBeRestarted": "{0} perlu dimuat ulang",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} dimulai",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} gagal",
|
||||||
|
"ProviderValue": "Penyedia: {0}",
|
||||||
|
"PluginUpdatedWithName": "{0} telah diperbarui",
|
||||||
|
"PluginInstalledWithName": "{0} telah dipasang",
|
||||||
|
"Plugin": "Plugin",
|
||||||
|
"Photos": "Foto",
|
||||||
|
"NotificationOptionUserLockedOut": "Pengguna terkunci",
|
||||||
|
"NotificationOptionTaskFailed": "Kegagalan tugas terjadwal",
|
||||||
|
"NotificationOptionServerRestartRequired": "Restart peladen dibutuhkan",
|
||||||
|
"NotificationOptionPluginUpdateInstalled": "Pembaruan plugin terpasang",
|
||||||
|
"NotificationOptionPluginInstalled": "Plugin terpasang",
|
||||||
|
"NotificationOptionPluginError": "Kegagalan plugin",
|
||||||
|
"NotificationOptionNewLibraryContent": "Konten baru ditambahkan",
|
||||||
|
"NotificationOptionInstallationFailed": "Kegagalan pemasangan",
|
||||||
|
"NotificationOptionCameraImageUploaded": "Gambar kamera terunggah",
|
||||||
|
"NotificationOptionApplicationUpdateInstalled": "Pembaruan aplikasi terpasang",
|
||||||
|
"NotificationOptionApplicationUpdateAvailable": "Pembaruan aplikasi tersedia",
|
||||||
|
"NewVersionIsAvailable": "Sebuah versi baru dari Peladen Jellyfin tersedia untuk diunduh.",
|
||||||
|
"NameSeasonUnknown": "Musim tak diketahui",
|
||||||
|
"NameSeasonNumber": "Musim {0}",
|
||||||
|
"NameInstallFailed": "{0} instalasi gagal",
|
||||||
|
"Music": "Musik",
|
||||||
|
"Movies": "Film",
|
||||||
|
"MessageServerConfigurationUpdated": "Konfigurasi peladen telah diperbarui",
|
||||||
|
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi peladen bagian {0} telah diperbarui",
|
||||||
|
"FailedLoginAttemptWithUserName": "Percobaan login gagal dari {0}",
|
||||||
|
"CameraImageUploadedFrom": "Sebuah gambar baru telah diunggah dari {0}",
|
||||||
|
"DeviceOfflineWithName": "{0} telah terputus",
|
||||||
|
"DeviceOnlineWithName": "{0} telah terhubung"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"Channels": "Canali",
|
"Channels": "Canali",
|
||||||
"ChapterNameValue": "Capitolo {0}",
|
"ChapterNameValue": "Capitolo {0}",
|
||||||
"Collections": "Collezioni",
|
"Collections": "Collezioni",
|
||||||
"DeviceOfflineWithName": "{0} ha disconnesso",
|
"DeviceOfflineWithName": "{0} si è disconnesso",
|
||||||
"DeviceOnlineWithName": "{0} è connesso",
|
"DeviceOnlineWithName": "{0} è connesso",
|
||||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||||
"Favorites": "Preferiti",
|
"Favorites": "Preferiti",
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
"ItemRemovedWithName": "{0} a fost eliminat din bibliotecă",
|
"ItemRemovedWithName": "{0} a fost eliminat din bibliotecă",
|
||||||
"ItemAddedWithName": "{0} a fost adăugat în bibliotecă",
|
"ItemAddedWithName": "{0} a fost adăugat în bibliotecă",
|
||||||
"Inherit": "Moștenit",
|
"Inherit": "Moștenit",
|
||||||
"HomeVideos": "Videoclipuri personale",
|
"HomeVideos": "Filme personale",
|
||||||
"HeaderRecordingGroups": "Grupuri de înregistrare",
|
"HeaderRecordingGroups": "Grupuri de înregistrare",
|
||||||
"HeaderLiveTV": "TV în Direct",
|
"HeaderLiveTV": "TV în Direct",
|
||||||
"HeaderFavoriteSongs": "Melodii Favorite",
|
"HeaderFavoriteSongs": "Melodii Favorite",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"DeviceOfflineWithName": "{0} je prekinil povezavo",
|
"DeviceOfflineWithName": "{0} je prekinil povezavo",
|
||||||
"DeviceOnlineWithName": "{0} je povezan",
|
"DeviceOnlineWithName": "{0} je povezan",
|
||||||
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
"FailedLoginAttemptWithUserName": "Neuspešen poskus prijave z {0}",
|
||||||
"Favorites": "Priljubljeni",
|
"Favorites": "Priljubljeno",
|
||||||
"Folders": "Mape",
|
"Folders": "Mape",
|
||||||
"Genres": "Zvrsti",
|
"Genres": "Zvrsti",
|
||||||
"HeaderAlbumArtists": "Izvajalci albuma",
|
"HeaderAlbumArtists": "Izvajalci albuma",
|
||||||
|
|
|
@ -79,7 +79,7 @@
|
||||||
"SubtitlesDownloadedForItem": "已为 {0} 下载了字幕",
|
"SubtitlesDownloadedForItem": "已为 {0} 下载了字幕",
|
||||||
"Sync": "同步",
|
"Sync": "同步",
|
||||||
"System": "系统",
|
"System": "系统",
|
||||||
"TvShows": "电视节目",
|
"TvShows": "电视剧",
|
||||||
"User": "用户",
|
"User": "用户",
|
||||||
"UserCreatedWithName": "用户 {0} 已创建",
|
"UserCreatedWithName": "用户 {0} 已创建",
|
||||||
"UserDeletedWithName": "用户 {0} 已删除",
|
"UserDeletedWithName": "用户 {0} 已删除",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Net.WebSockets;
|
|
||||||
using MediaBrowser.Model.Services;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Net
|
namespace Emby.Server.Implementations.Net
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Server.Implementations.Images;
|
using Emby.Server.Implementations.Images;
|
||||||
|
|
|
@ -70,7 +70,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the task to be executed
|
/// Returns the task to be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <param name="progress">The progress.</param>
|
/// <param name="progress">The progress.</param>
|
||||||
|
@ -89,7 +89,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
SourceTypes = new SourceType[] { SourceType.Library },
|
SourceTypes = new SourceType[] { SourceType.Library },
|
||||||
HasChapterImages = false,
|
HasChapterImages = false,
|
||||||
IsVirtualItem = false
|
IsVirtualItem = false
|
||||||
|
|
||||||
})
|
})
|
||||||
.OfType<Video>()
|
.OfType<Video>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
@ -160,7 +159,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Chapter image extraction";
|
public string Name => "Extract Chapter Images";
|
||||||
|
|
||||||
public string Description => "Creates thumbnails for videos that have chapters.";
|
public string Description => "Creates thumbnails for videos that have chapters.";
|
||||||
|
|
||||||
|
|
|
@ -158,9 +158,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Cache file cleanup";
|
public string Name => "Clean Cache Directory";
|
||||||
|
|
||||||
public string Description => "Deletes cache files no longer needed by the system";
|
public string Description => "Deletes cache files no longer needed by the system.";
|
||||||
|
|
||||||
public string Category => "Maintenance";
|
public string Category => "Maintenance";
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ using MediaBrowser.Model.Tasks;
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes old log files
|
/// Deletes old log files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
|
public class DeleteLogFileTask : IScheduledTask, IConfigurableScheduledTask
|
||||||
{
|
{
|
||||||
|
@ -33,20 +33,18 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[] {
|
return new[] {
|
||||||
|
|
||||||
// Every so often
|
|
||||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the task to be executed
|
/// Returns the task to be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <param name="progress">The progress.</param>
|
/// <param name="progress">The progress.</param>
|
||||||
|
@ -81,7 +79,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Log file cleanup";
|
public string Name => "Clean Log Directory";
|
||||||
|
|
||||||
public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
|
public string Description => string.Format("Deletes log files that are more than {0} days old.", ConfigurationManager.CommonConfiguration.LogFileRetentionDays);
|
||||||
|
|
||||||
|
|
|
@ -125,9 +125,9 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Transcode file cleanup";
|
public string Name => "Clean Transcode Directory";
|
||||||
|
|
||||||
public string Description => "Deletes transcode files more than 24 hours old.";
|
public string Description => "Deletes transcode files more than one day old.";
|
||||||
|
|
||||||
public string Category => "Maintenance";
|
public string Category => "Maintenance";
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@ using MediaBrowser.Model.Tasks;
|
||||||
namespace Emby.Server.Implementations.ScheduledTasks
|
namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class PeopleValidationTask
|
/// Class PeopleValidationTask.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PeopleValidationTask : IScheduledTask
|
public class PeopleValidationTask : IScheduledTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _library manager
|
/// The library manager.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
|
||||||
|
@ -32,13 +32,12 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the triggers that define when the task will run
|
/// Creates the triggers that define when the task will run.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
// Every so often
|
|
||||||
new TaskTriggerInfo
|
new TaskTriggerInfo
|
||||||
{
|
{
|
||||||
Type = TaskTriggerInfo.TriggerInterval,
|
Type = TaskTriggerInfo.TriggerInterval,
|
||||||
|
@ -48,7 +47,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the task to be executed
|
/// Returns the task to be executed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <param name="progress">The progress.</param>
|
/// <param name="progress">The progress.</param>
|
||||||
|
@ -58,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
return _libraryManager.ValidatePeople(cancellationToken, progress);
|
return _libraryManager.ValidatePeople(cancellationToken, progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Refresh people";
|
public string Name => "Refresh People";
|
||||||
|
|
||||||
public string Description => "Updates metadata for actors and directors in your media library.";
|
public string Description => "Updates metadata for actors and directors in your media library.";
|
||||||
|
|
||||||
|
|
|
@ -96,7 +96,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Name => "Check for plugin updates";
|
public string Name => "Update Plugins";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
public string Description => "Downloads and installs updates for plugins that are configured to update automatically.";
|
||||||
|
|
|
@ -57,7 +57,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||||
return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
|
return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "Scan media library";
|
public string Name => "Scan Media Library";
|
||||||
|
|
||||||
public string Description => "Scans your media library for new files and refreshes metadata.";
|
public string Description => "Scans your media library for new files and refreshes metadata.";
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Serialization
|
namespace Emby.Server.Implementations.Serialization
|
||||||
|
@ -12,13 +11,15 @@ namespace Emby.Server.Implementations.Serialization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class JsonSerializer : IJsonSerializer
|
public class JsonSerializer : IJsonSerializer
|
||||||
{
|
{
|
||||||
private readonly IFileSystem _fileSystem;
|
public JsonSerializer()
|
||||||
|
|
||||||
public JsonSerializer(
|
|
||||||
IFileSystem fileSystem)
|
|
||||||
{
|
{
|
||||||
_fileSystem = fileSystem;
|
ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
|
||||||
Configure();
|
ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
|
||||||
|
ServiceStack.Text.JsConfig.IncludeNullValues = false;
|
||||||
|
ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
|
||||||
|
ServiceStack.Text.JsConfig.AssumeUtc = true;
|
||||||
|
|
||||||
|
ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Serialization
|
||||||
throw new ArgumentNullException(nameof(file));
|
throw new ArgumentNullException(nameof(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var stream = _fileSystem.GetFileStream(file, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
|
using (var stream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read))
|
||||||
{
|
{
|
||||||
SerializeToStream(obj, stream);
|
SerializeToStream(obj, stream);
|
||||||
}
|
}
|
||||||
|
@ -162,7 +163,6 @@ namespace Emby.Server.Implementations.Serialization
|
||||||
throw new ArgumentNullException(nameof(stream));
|
throw new ArgumentNullException(nameof(stream));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return ServiceStack.Text.JsonSerializer.DeserializeFromStreamAsync<T>(stream);
|
return ServiceStack.Text.JsonSerializer.DeserializeFromStreamAsync<T>(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,20 +225,6 @@ namespace Emby.Server.Implementations.Serialization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Configures this instance.
|
|
||||||
/// </summary>
|
|
||||||
private void Configure()
|
|
||||||
{
|
|
||||||
ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.DateHandler.ISO8601;
|
|
||||||
ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
|
|
||||||
ServiceStack.Text.JsConfig.IncludeNullValues = false;
|
|
||||||
ServiceStack.Text.JsConfig.AlwaysUseUtc = true;
|
|
||||||
ServiceStack.Text.JsConfig.AssumeUtc = true;
|
|
||||||
|
|
||||||
ServiceStack.Text.JsConfig<Guid>.SerializeFn = SerializeGuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string SerializeGuid(Guid guid)
|
private static string SerializeGuid(Guid guid)
|
||||||
{
|
{
|
||||||
if (guid.Equals(Guid.Empty))
|
if (guid.Equals(Guid.Empty))
|
||||||
|
|
|
@ -1704,7 +1704,7 @@ namespace Emby.Server.Implementations.Session
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError("Error getting {0} image info", ex, type);
|
_logger.LogError(ex, "Error getting image information for {Type}", type);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue