aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-compat.yml96
-rw-r--r--.ci/azure-pipelines-main.yml101
-rw-r--r--.ci/azure-pipelines-test.yml65
-rw-r--r--.ci/azure-pipelines-windows.yml82
-rw-r--r--.ci/azure-pipelines.yml326
-rw-r--r--.ci/publish-nightly.yml46
-rw-r--r--.ci/publish-release.yml48
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Dockerfile.arm6
-rw-r--r--Dockerfile.arm646
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs4
-rw-r--r--Emby.Naming/Common/NamingOptions.cs7
-rw-r--r--Emby.Naming/Subtitles/SubtitleParser.cs3
-rw-r--r--Emby.Naming/TV/EpisodePathParser.cs2
-rw-r--r--Emby.Naming/Video/CleanDateTimeParser.cs71
-rw-r--r--Emby.Naming/Video/CleanDateTimeResult.cs29
-rw-r--r--Emby.Naming/Video/CleanStringParser.cs41
-rw-r--r--Emby.Naming/Video/CleanStringResult.cs20
-rw-r--r--Emby.Naming/Video/VideoResolver.cs11
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs21
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs7
-rw-r--r--tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs21
-rw-r--r--tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs31
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs8
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs172
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs160
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs1
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/StackTests.cs26
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
-rw-r--r--tests/coverletArgs.runsettings17
32 files changed, 558 insertions, 876 deletions
diff --git a/.ci/azure-pipelines-compat.yml b/.ci/azure-pipelines-compat.yml
new file mode 100644
index 000000000..762bbdcb2
--- /dev/null
+++ b/.ci/azure-pipelines-compat.yml
@@ -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)
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
new file mode 100644
index 000000000..09901b2a6
--- /dev/null
+++ b/.ci/azure-pipelines-main.yml
@@ -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"
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
new file mode 100644
index 000000000..4455632e1
--- /dev/null
+++ b/.ci/azure-pipelines-test.yml
@@ -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
diff --git a/.ci/azure-pipelines-windows.yml b/.ci/azure-pipelines-windows.yml
new file mode 100644
index 000000000..32d1d1382
--- /dev/null
+++ b/.ci/azure-pipelines-windows.yml
@@ -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"
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index 7bcaed70c..f79a85b21 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -2,9 +2,11 @@ name: $(Date:yyyyMMdd)$(Rev:.r)
variables:
- name: TestProjects
- value: 'tests/**/*Tests.csproj'
+ value: "tests/**/*Tests.csproj"
- name: RestoreBuildProjects
- value: 'Jellyfin.Server/Jellyfin.Server.csproj'
+ value: "Jellyfin.Server/Jellyfin.Server.csproj"
+ - name: DotNetSdkVersion
+ value: 3.1.100
pr:
autoCancel: true
@@ -13,234 +15,26 @@ trigger:
batch: true
jobs:
- - job: main_build
- displayName: Main Build
- pool:
- vmImage: ubuntu-latest
- strategy:
- matrix:
- Release:
- BuildConfiguration: Release
- Debug:
- BuildConfiguration: Debug
- maxParallel: 2
- steps:
- - checkout: self
- clean: true
- submodules: true
- persistCredentials: true
-
- - task: CmdLine@2
- displayName: "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 # 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:
+ - template: azure-pipelines-main.yml
+ parameters:
+ LinuxImage: "ubuntu-latest"
+ RestoreBuildProjects: $(RestoreBuildProjects)
+
+ - template: azure-pipelines-test.yml
+ parameters:
+ ImageNames:
+ Linux: "ubuntu-latest"
+ Windows: "windows-latest"
+ macOS: "macos-latest"
+
+ - template: azure-pipelines-windows.yml
+ parameters:
+ WindowsImage: "windows-latest"
+ TestProjects: $(TestProjects)
+
+ - template: azure-pipelines-compat.yml
+ parameters:
+ Packages:
Naming:
NugetPackageName: Jellyfin.Naming
AssemblyFileName: Emby.Naming.dll
@@ -253,74 +47,4 @@ jobs:
Common:
NugetPackageName: Jellyfin.Common
AssemblyFileName: MediaBrowser.Common.dll
- maxParallel: 2
- 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
+ LinuxImage: "ubuntu-latest"
diff --git a/.ci/publish-nightly.yml b/.ci/publish-nightly.yml
deleted file mode 100644
index a693e10f6..000000000
--- a/.ci/publish-nightly.yml
+++ /dev/null
@@ -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'
diff --git a/.ci/publish-release.yml b/.ci/publish-release.yml
deleted file mode 100644
index 57e77ae5a..000000000
--- a/.ci/publish-release.yml
+++ /dev/null
@@ -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'
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 458944778..800b3d51f 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -32,6 +32,7 @@
- [nevado](https://github.com/nevado)
- [mark-monteiro](https://github.com/mark-monteiro)
- [ullmie02](https://github.com/ullmie02)
+ - [pR0Ps](https://github.com/pR0Ps)
# Emby Contributors
diff --git a/Dockerfile.arm b/Dockerfile.arm
index 0e6236628..551aa177a 100644
--- a/Dockerfile.arm
+++ b/Dockerfile.arm
@@ -1,5 +1,3 @@
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1
@@ -23,9 +21,7 @@ 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"
-FROM multiarch/qemu-user-static:x86_64-arm as qemu
-FROM debian:stretch-slim
-COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
+FROM debian:buster-slim
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
libssl-dev \
diff --git a/Dockerfile.arm64 b/Dockerfile.arm64
index 796d12f98..4c2ca12a6 100644
--- a/Dockerfile.arm64
+++ b/Dockerfile.arm64
@@ -1,5 +1,3 @@
-# Requires binfm_misc registration
-# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
ARG DOTNET_VERSION=3.1
@@ -23,9 +21,7 @@ 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"
-FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
-FROM debian:stretch-slim
-COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
+FROM debian:buster-slim
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ffmpeg \
libssl-dev \
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index 7ed46984a..396649c5e 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -771,11 +771,11 @@ namespace Emby.Dlna.ContentDirectory
})
.ToArray();
- return new QueryResult<ServerItem>
+ return ApplyPaging(new QueryResult<ServerItem>
{
Items = folders,
TotalRecordCount = folders.Length
- };
+ }, startIndex, limit);
}
private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index a2105889b..9ce503b8e 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -177,13 +177,12 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](\d{4})([ _\,\.\(\)\[\]\-][^\d]|).*(\d{4})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
};
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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
+ @"[ _\,\.\(\)\[\]\-](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|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"(\[.*\])"
};
@@ -340,7 +339,7 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming
-                // [bar] Foo - 1 [baz]
+ // [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
{
IsNamed = true
diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs
index 99680c622..b055b1a6c 100644
--- a/Emby.Naming/Subtitles/SubtitleParser.cs
+++ b/Emby.Naming/Subtitles/SubtitleParser.cs
@@ -31,7 +31,6 @@ namespace Emby.Naming.Subtitles
}
var flags = GetFlags(path);
-
var info = new SubtitleInfo
{
Path = path,
@@ -45,7 +44,7 @@ namespace Emby.Naming.Subtitles
// Should have a name, language and file extension
if (parts.Count >= 3)
{
- info.Language = parts[parts.Count - 2];
+ info.Language = parts[^2];
}
return info;
diff --git a/Emby.Naming/TV/EpisodePathParser.cs b/Emby.Naming/TV/EpisodePathParser.cs
index 4fac543f9..6b557d2e1 100644
--- a/Emby.Naming/TV/EpisodePathParser.cs
+++ b/Emby.Naming/TV/EpisodePathParser.cs
@@ -131,7 +131,7 @@ namespace Emby.Naming.TV
var endingNumberGroup = match.Groups["endingepnumber"];
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.
// 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;
diff --git a/Emby.Naming/Video/CleanDateTimeParser.cs b/Emby.Naming/Video/CleanDateTimeParser.cs
index a9db4cccc..6c74c07d5 100644
--- a/Emby.Naming/Video/CleanDateTimeParser.cs
+++ b/Emby.Naming/Video/CleanDateTimeParser.cs
@@ -1,89 +1,48 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
+#nullable enable
-using System;
+using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
-using Emby.Naming.Common;
namespace Emby.Naming.Video
{
/// <summary>
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </summary>
- public class CleanDateTimeParser
+ public static class CleanDateTimeParser
{
- private readonly NamingOptions _options;
-
- public CleanDateTimeParser(NamingOptions options)
+ public static CleanDateTimeResult Clean(string name, IReadOnlyList<Regex> cleanDateTimeRegexes)
{
- _options = options;
- }
-
- public CleanDateTimeResult Clean(string name)
- {
- var originalName = name;
-
- try
+ CleanDateTimeResult result = new CleanDateTimeResult(name);
+ var len = cleanDateTimeRegexes.Count;
+ for (int i = 0; i < len; i++)
{
- var extension = Path.GetExtension(name) ?? string.Empty;
- // Check supported extensions
- if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)
- && !_options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
+ if (TryClean(name, cleanDateTimeRegexes[i], ref result))
{
- // Dummy up a file extension because the expressions will fail without one
- // 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";
+ return result;
}
}
- catch (ArgumentException)
- {
- }
-
- var result = _options.CleanDateTimeRegexes.Select(i => Clean(name, i))
- .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;
+ return 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);
if (match.Success
- && match.Groups.Count == 4
+ && match.Groups.Count == 5
&& match.Groups[1].Success
&& match.Groups[2].Success
&& int.TryParse(match.Groups[2].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
- name = match.Groups[1].Value;
- result.Year = year;
- result.HasChanged = true;
+ result = new CleanDateTimeResult(match.Groups[1].Value.TrimEnd(), year);
+ return true;
}
- result.Name = name;
- return result;
+ return false;
}
}
}
diff --git a/Emby.Naming/Video/CleanDateTimeResult.cs b/Emby.Naming/Video/CleanDateTimeResult.cs
index a7581972e..73a445612 100644
--- a/Emby.Naming/Video/CleanDateTimeResult.cs
+++ b/Emby.Naming/Video/CleanDateTimeResult.cs
@@ -1,26 +1,33 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
+#nullable enable
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>
- /// Gets or sets the name.
+ /// Gets the name.
/// </summary>
/// <value>The name.</value>
- public string Name { get; set; }
+ public string Name { get; }
/// <summary>
- /// Gets or sets the year.
+ /// Gets the year.
/// </summary>
/// <value>The year.</value>
- public int? Year { 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; }
+ public int? Year { get; }
}
}
diff --git a/Emby.Naming/Video/CleanStringParser.cs b/Emby.Naming/Video/CleanStringParser.cs
index fcd4b65c7..b7b65d822 100644
--- a/Emby.Naming/Video/CleanStringParser.cs
+++ b/Emby.Naming/Video/CleanStringParser.cs
@@ -1,6 +1,8 @@
#pragma warning disable CS1591
#pragma warning disable SA1600
+#nullable enable
+using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
@@ -9,44 +11,35 @@ namespace Emby.Naming.Video
/// <summary>
/// <see href="http://kodi.wiki/view/Advancedsettings.xml#video" />.
/// </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;
-
- foreach (var exp in expressions)
+ var len = expressions.Count;
+ for (int i = 0; i < len; i++)
{
- var result = Clean(name, exp);
-
- if (!string.IsNullOrEmpty(result.Name))
+ if (TryClean(name, expressions[i], out newName))
{
- name = result.Name;
- hasChanged = hasChanged || result.HasChanged;
+ return true;
}
}
- return new CleanStringResult
- {
- Name = name,
- HasChanged = hasChanged
- };
+ newName = ReadOnlySpan<char>.Empty;
+ return false;
}
- 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);
-
- if (match.Success)
+ int index = match.Index;
+ if (match.Success && index != 0)
{
- result.HasChanged = true;
- name = name.Substring(0, match.Index);
+ newName = name.AsSpan().Slice(0, match.Index);
+ return true;
}
- result.Name = name;
- return result;
+ newName = string.Empty;
+ return false;
}
}
}
diff --git a/Emby.Naming/Video/CleanStringResult.cs b/Emby.Naming/Video/CleanStringResult.cs
deleted file mode 100644
index 786fe9e02..000000000
--- a/Emby.Naming/Video/CleanStringResult.cs
+++ /dev/null
@@ -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; }
- }
-}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 41b79697c..f93db2486 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -94,9 +94,10 @@ namespace Emby.Naming.Video
{
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;
@@ -130,14 +131,14 @@ namespace Emby.Naming.Video
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)
{
- return new CleanDateTimeParser(_options).Clean(name);
+ return CleanDateTimeParser.Clean(name, _options.CleanDateTimeRegexes);
}
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index ae3cdece9..6fb623554 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -36,7 +36,6 @@ using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Net;
@@ -54,6 +53,9 @@ namespace Emby.Server.Implementations.Library
/// </summary>
public class LibraryManager : ILibraryManager
{
+ private NamingOptions _namingOptions;
+ private string[] _videoFileExtensions;
+
/// <summary>
/// Gets or sets the postscan tasks.
/// </summary>
@@ -2509,20 +2511,10 @@ namespace Emby.Server.Implementations.Library
public NamingOptions GetNamingOptions()
{
- return GetNamingOptionsInternal();
- }
-
- private NamingOptions _namingOptions;
- private string[] _videoFileExtensions;
-
- private NamingOptions GetNamingOptionsInternal()
- {
if (_namingOptions == null)
{
- var options = new NamingOptions();
-
- _namingOptions = options;
- _videoFileExtensions = _namingOptions.VideoFileExtensions.ToArray();
+ _namingOptions = new NamingOptions();
+ _videoFileExtensions = _namingOptions.VideoFileExtensions;
}
return _namingOptions;
@@ -2533,11 +2525,10 @@ namespace Emby.Server.Implementations.Library
var resolver = new VideoResolver(GetNamingOptions());
var result = resolver.CleanDateTime(name);
- var cleanName = resolver.CleanString(result.Name);
return new ItemLookupInfo
{
- Name = cleanName.Name,
+ Name = resolver.TryCleanString(result.Name, out var newName) ? newName.ToString() : result.Name,
Year = result.Year
};
}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 10f10a15e..15880a9a1 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -275,7 +275,7 @@ namespace MediaBrowser.Api.Playback
{
// TODO handle supportedLiveMediaTypes?
- var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, false, CancellationToken.None).ConfigureAwait(false);
+ var mediaSourcesList = await _mediaSourceManager.GetPlaybackMediaSources(item, user, true, true, CancellationToken.None).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(mediaSourceId))
{
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 3b8cb93fc..acc89e352 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -2757,7 +2757,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!state.RunTimeTicks.HasValue)
{
- args += " -flags -global_header -fflags +genpts";
+ args += " -fflags +genpts";
}
}
else
@@ -2802,11 +2802,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
args += " " + qualityParam.Trim();
}
-
- if (!state.RunTimeTicks.HasValue)
- {
- args += " -flags -global_header";
- }
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
diff --git a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
index eb69d915c..a79e2cf61 100644
--- a/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Music/MultiDiscAlbumTests.cs
@@ -10,18 +10,27 @@ namespace Jellyfin.Naming.Tests.Music
public void TestMultiDiscAlbums()
{
Assert.False(IsMultiDiscAlbumFolder(@"blah blah"));
- Assert.False(IsMultiDiscAlbumFolder(@"d:/music\weezer/03 Pinkerton"));
- Assert.False(IsMultiDiscAlbumFolder(@"d:/music/michael jackson/Bad (2012 Remaster)"));
+ Assert.False(IsMultiDiscAlbumFolder(@"D:/music/weezer/03 Pinkerton"));
+ Assert.False(IsMultiDiscAlbumFolder(@"D:/music/michael jackson/Bad (2012 Remaster)"));
Assert.True(IsMultiDiscAlbumFolder(@"cd1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disc1"));
- Assert.True(IsMultiDiscAlbumFolder(@"disk1"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disc18"));
+ Assert.True(IsMultiDiscAlbumFolder(@"disk10"));
+ Assert.True(IsMultiDiscAlbumFolder(@"vol7"));
+ Assert.True(IsMultiDiscAlbumFolder(@"volume1"));
- // Add a space
Assert.True(IsMultiDiscAlbumFolder(@"cd 1"));
Assert.True(IsMultiDiscAlbumFolder(@"disc 1"));
Assert.True(IsMultiDiscAlbumFolder(@"disk 1"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk ·"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk a"));
+
+ Assert.False(IsMultiDiscAlbumFolder(@"disk volume"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disc disc"));
+ Assert.False(IsMultiDiscAlbumFolder(@"disk disc 6"));
+
Assert.True(IsMultiDiscAlbumFolder(@"cd - 1"));
Assert.True(IsMultiDiscAlbumFolder(@"disc- 1"));
Assert.True(IsMultiDiscAlbumFolder(@"disk - 1"));
@@ -38,7 +47,7 @@ namespace Jellyfin.Naming.Tests.Music
[Fact]
public void TestMultiDiscAlbums1()
{
- Assert.False(IsMultiDiscAlbumFolder(@"[1985] Oppurtunities (Let's make lots of money) (1985)"));
+ Assert.False(IsMultiDiscAlbumFolder(@"[1985] Opportunities (Let's make lots of money) (1985)"));
}
[Fact]
diff --git a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
index e8f14cdc4..41da889c2 100644
--- a/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Subtitles/SubtitleParserTests.cs
@@ -22,7 +22,6 @@ namespace Jellyfin.Naming.Tests.Subtitles
Test("The Skin I Live In (2011).eng.forced.srt", "eng", false, true);
Test("The Skin I Live In (2011).eng.foreign.srt", "eng", false, true);
Test("The Skin I Live In (2011).eng.default.foreign.srt", "eng", true, true);
-
Test("The Skin I Live In (2011).default.foreign.eng.srt", "eng", true, true);
}
diff --git a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
index 1ae637281..93c59c9ca 100644
--- a/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
+++ b/tests/Jellyfin.Naming.Tests/TV/EpisodeNumberTests.cs
@@ -61,21 +61,6 @@ namespace Jellyfin.Naming.Tests.TV
}
[Fact]
- public void TestEpisodeNumber50()
- {
- // This convention is not currently supported, just adding in case we want to look at it in the future
- Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season s2016e1.mp4"));
- }
-
- // FIXME
- // [Fact]
- public void TestEpisodeNumber51()
- {
- // This convention is not currently supported, just adding in case we want to look at it in the future
- Assert.Equal(1, GetEpisodeNumberFromFile(@"2016/Season 2016x1.mp4"));
- }
-
- [Fact]
public void TestEpisodeNumber52()
{
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode - 16.avi"));
@@ -84,31 +69,15 @@ namespace Jellyfin.Naming.Tests.TV
[Fact]
public void TestEpisodeNumber53()
{
- // This is not supported. Expected to fail, although it would be a good one to add support for.
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16.avi"));
}
[Fact]
public void TestEpisodeNumber54()
{
- // This is not supported. Expected to fail, although it would be a good one to add support for.
Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Episode 16 - Some Title.avi"));
}
- // [Fact]
- public void TestEpisodeNumber55()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16.avi"));
- }
-
- // [Fact]
- public void TestEpisodeNumber56()
- {
- // This is not supported. Expected to fail, although it would be a good one to add support for.
- Assert.Equal(16, GetEpisodeNumberFromFile(@"Season 2/Season 3 Episode 16 - Some Title.avi"));
- }
-
[Fact]
public void TestEpisodeNumber57()
{
diff --git a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
index b993e241c..0c2978aca 100644
--- a/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/BaseVideoTest.cs
@@ -5,11 +5,9 @@ namespace Jellyfin.Naming.Tests.Video
{
public abstract class BaseVideoTest
{
- protected VideoResolver GetParser()
- {
- var options = new NamingOptions();
+ private readonly NamingOptions _namingOptions = new NamingOptions();
- return new VideoResolver(options);
- }
+ protected VideoResolver GetParser()
+ => new VideoResolver(_namingOptions);
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
index bba73ad91..a2ef2dcd6 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -1,143 +1,59 @@
using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
{
- public class CleanDateTimeTests : BaseVideoTest
+ public sealed class CleanDateTimeTests
{
- // FIXME
- // [Fact]
- public void TestCleanDateTime()
- {
- Test(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013);
- Test(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013);
- Test(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013);
- Test(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013);
-
- Test(@"300 (2006).mkv", "300", 2006);
- Test(@"d:/movies/300 (2006).mkv", "300", 2006);
- Test(@"300 2 (2006).mkv", "300 2", 2006);
- Test(@"300 - 2 (2006).mkv", "300 - 2", 2006);
- Test(@"300 2001 (2006).mkv", "300 2001", 2006);
-
- Test(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013);
- Test(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013);
-
- Test(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTime1()
- {
- Test(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutFileExtension()
- {
- Test(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013);
- Test(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013);
- Test(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013);
- Test(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013);
-
- Test(@"300 (2006)", "300", 2006);
- Test(@"d:/movies/300 (2006)", "300", 2006);
- Test(@"300 2 (2006)", "300 2", 2006);
- Test(@"300 - 2 (2006)", "300 - 2", 2006);
- Test(@"300 2001 (2006)", "300 2001", 2006);
-
- Test(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006);
- Test(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006);
- }
-
- [Fact]
- public void TestCleanDateTimeWithoutDate()
- {
- Test(@"American.Psycho.mkv", "American.Psycho.mkv", null);
- Test(@"American Psycho.mkv", "American Psycho.mkv", null);
- }
-
- [Fact]
- public void TestCleanDateTimeWithBracketedName()
- {
- Test(@"[rec].mkv", "[rec].mkv", null);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutExtension()
- {
- Test(@"St. Vincent (2014)", "St. Vincent", 2014);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutDate1()
- {
- Test("Super movie(2009).mp4", "Super movie", 2009);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithoutParenthesis()
- {
- Test("Drug War 2013.mp4", "Drug War", 2013);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithMultipleYears()
- {
- Test("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithYearAndResolution()
- {
- Test("First Man 2018 1080p.mkv", "First Man", 2018);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithYearAndResolution1()
- {
- Test("First Man (2018) 1080p.mkv", "First Man", 2018);
- }
-
- // FIXME
- // [Fact]
- public void TestCleanDateTimeWithSceneRelease()
- {
- Test("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016);
- }
-
- // FIXME
- // [Fact]
- public void TestYearInBrackets()
- {
- Test("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018);
- }
-
- private void Test(string input, string expectedName, int? expectedYear)
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData(@"The Wolf of Wall Street (2013).mkv", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013).mkv", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013).mkv", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013).mkv", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006).mkv", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"300 2 (2006).mkv", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006).mkv", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006).mkv", "300 2001", 2006)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.1080p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"curse.of.chucky.2013.stv.unrated.multi.2160p.bluray.x264-rough", "curse.of.chucky", 2013)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).bluray.disc", "300", 2006)]
+ [InlineData(@"Arrival.2016.2160p.Blu-Ray.HEVC.mkv", "Arrival", 2016)]
+ [InlineData(@"The Wolf of Wall Street (2013)", "The Wolf of Wall Street", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2 (2013)", "The Wolf of Wall Street 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street - 2 (2013)", "The Wolf of Wall Street - 2", 2013)]
+ [InlineData(@"The Wolf of Wall Street 2001 (2013)", "The Wolf of Wall Street 2001", 2013)]
+ [InlineData(@"300 (2006)", "300", 2006)]
+ [InlineData(@"d:/movies/300 (2006)", "300", 2006)]
+ [InlineData(@"300 2 (2006)", "300 2", 2006)]
+ [InlineData(@"300 - 2 (2006)", "300 - 2", 2006)]
+ [InlineData(@"300 2001 (2006)", "300 2001", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006)", "300", 2006)]
+ [InlineData(@"/server/Movies/300 (2007)/300 (2006).mkv", "300", 2006)]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv", null)]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv", null)]
+ [InlineData(@"[rec].mkv", "[rec].mkv", null)]
+ [InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
+ [InlineData("Super movie(2009).mp4", "Super movie", 2009)]
+ // FIXME: [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
+ [InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
+ // FIXME: [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
+ [InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
+ // FIXME: [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
+ // FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
+ [InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
+ public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)
{
input = Path.GetFileName(input);
- var result = GetParser().CleanDateTime(input);
+ var result = new VideoResolver(_namingOptions).CleanDateTime(input);
Assert.Equal(expectedName, result.Name, true);
Assert.Equal(expectedYear, result.Year);
}
-
- // FIXME
- // [Fact]
- public void TestCleanDateAndStringsSequence()
- {
- // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
-
- Test(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014);
- }
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
index cd90ac236..fde06c5a1 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanStringTests.cs
@@ -1,133 +1,45 @@
using System;
-using System.Globalization;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
using Xunit;
namespace Jellyfin.Naming.Tests.Video
{
- public class CleanStringTests : BaseVideoTest
+ public sealed class CleanStringTests
{
- // FIXME
- // [Fact]
- public void TestCleanString()
- {
- Test("Super movie 480p.mp4", "Super movie");
- Test("Super movie 480p 2001.mp4", "Super movie");
- Test("Super movie [480p].mp4", "Super movie");
- Test("480 Super movie [tmdbid=12345].mp4", "480 Super movie");
- }
-
- // FIXME
- // [Fact]
- public void TestCleanString1()
- {
- Test("Super movie(2009).mp4", "Super movie(2009).mp4");
- }
-
- // FIXME
- // [Fact]
- public void TestCleanString2()
- {
- Test("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4");
- }
-
- // FIXME
- // [Fact]
- public void TestStringWithoutDate()
- {
- Test(@"American.Psycho.mkv", "American.Psycho.mkv");
- Test(@"American Psycho.mkv", "American Psycho.mkv");
- }
-
- // FIXME
- // [Fact]
- public void TestNameWithBrackets()
- {
- Test(@"[rec].mkv", "[rec].mkv");
- }
-
- // FIXME
- // [Fact]
- public void Test4k()
- {
- Test("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestUltraHd()
- {
- Test("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestUHd()
- {
- Test("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDR()
- {
- Test("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDC()
- {
- Test("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestHDC1()
- {
- Test("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestBDrip()
- {
- Test("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestBDripHDC()
- {
- Test("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestMulti()
- {
- Test("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon");
- }
-
- // FIXME
- // [Fact]
- public void TestLeadingBraces()
- {
- // Not actually supported, just reported by a user
- Test("[0004] - After The Sunset.el.mkv", "After The Sunset");
- }
-
- // FIXME
- // [Fact]
- public void TestTrailingBraces()
- {
- Test("After The Sunset - [0004].mkv", "After The Sunset");
- }
-
- private void Test(string input, string expectedName)
- {
- var result = GetParser().CleanString(input).ToString();
-
- Assert.Equal(expectedName, result, true);
+ private readonly NamingOptions _namingOptions = new NamingOptions();
+
+ [Theory]
+ [InlineData("Super movie 480p.mp4", "Super movie")]
+ [InlineData("Super movie 480p 2001.mp4", "Super movie")]
+ [InlineData("Super movie [480p].mp4", "Super movie")]
+ [InlineData("480 Super movie [tmdbid=12345].mp4", "480 Super movie")]
+ [InlineData("Super movie(2009).mp4", "Super movie(2009).mp4")]
+ [InlineData("Run lola run (lola rennt) (2009).mp4", "Run lola run (lola rennt) (2009).mp4")]
+ [InlineData(@"American.Psycho.mkv", "American.Psycho.mkv")]
+ [InlineData(@"American Psycho.mkv", "American Psycho.mkv")]
+ [InlineData(@"[rec].mkv", "[rec].mkv")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4k.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UltraHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.UHD.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDR.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ [InlineData("Crouching.Tiger.Hidden.Dragon.4K.UltraHD.HDR.BDrip-HDC.mkv", "Crouching.Tiger.Hidden.Dragon")]
+ // FIXME: [InlineData("After The Sunset - [0004].mkv", "After The Sunset")]
+ public void CleanStringTest(string input, string expectedName)
+ {
+ if (new VideoResolver(_namingOptions).TryCleanString(input, out ReadOnlySpan<char> newName))
+ {
+ // TODO: compare spans when XUnit supports it
+ Assert.Equal(expectedName, newName.ToString());
+ }
+ else
+ {
+ Assert.Equal(expectedName, input);
+ }
}
}
}
diff --git a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
index b8674ec49..b8fbb2cb2 100644
--- a/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/MultiVersionTests.cs
@@ -62,7 +62,6 @@ namespace Jellyfin.Naming.Tests.Video
[Fact]
public void TestMultiEdition3()
{
- // This is currently not supported and will fail, but we should try to figure it out
var files = new[]
{
@"/movies/The Phantom of the Opera (1925)/The Phantom of the Opera (1925) - 1925 version.mkv",
diff --git a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
index 5faef0e3d..5c121d738 100644
--- a/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/StackTests.cs
@@ -383,32 +383,6 @@ namespace Jellyfin.Naming.Tests.Video
}
[Fact]
- public void TestDirectories2()
- {
- //TestDirectory(@"blah blah", false, @"blah blah");
- //TestDirectory(@"d:/music/weezer/03 Pinkerton", false, "03 Pinkerton");
- //TestDirectory(@"d:/music/michael jackson/Bad (2012 Remaster)", false, "Bad (2012 Remaster)");
-
- //TestDirectory(@"blah blah - cd1", true, "blah blah");
- //TestDirectory(@"blah blah - disc1", true, "blah blah");
- //TestDirectory(@"blah blah - disk1", true, "blah blah");
- //TestDirectory(@"blah blah - pt1", true, "blah blah");
- //TestDirectory(@"blah blah - part1", true, "blah blah");
- //TestDirectory(@"blah blah - dvd1", true, "blah blah");
-
- //// Add a space
- //TestDirectory(@"blah blah - cd 1", true, "blah blah");
- //TestDirectory(@"blah blah - disc 1", true, "blah blah");
- //TestDirectory(@"blah blah - disk 1", true, "blah blah");
- //TestDirectory(@"blah blah - pt 1", true, "blah blah");
- //TestDirectory(@"blah blah - part 1", true, "blah blah");
- //TestDirectory(@"blah blah - dvd 1", true, "blah blah");
-
- //// Not case sensitive
- //TestDirectory(@"blah blah - Disc1", true, "blah blah");
- }
-
- [Fact]
public void TestNamesWithoutParts()
{
// No stacking here because there is no part/disc/etc
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index bb2afea16..f62d3dcbc 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -2,9 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
-
<IsPackable>false</IsPackable>
-
<RootNamespace>Jellyfin.Server.Implementations.Tests</RootNamespace>
</PropertyGroup>
diff --git a/tests/coverletArgs.runsettings b/tests/coverletArgs.runsettings
new file mode 100644
index 000000000..3113957e0
--- /dev/null
+++ b/tests/coverletArgs.runsettings
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<RunSettings>
+ <DataCollectionRunSettings>
+ <DataCollectors>
+ <DataCollector friendlyName="XPlat code coverage">
+ <Configuration>
+ <Format>cobertura</Format>
+ <Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[*]Moq*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
+ <ExcludeByAttribute>Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute</ExcludeByAttribute>
+ <SingleHit>false</SingleHit>
+ <UseSourceLink>true</UseSourceLink>
+ <IncludeTestAssembly>false</IncludeTestAssembly>
+ </Configuration>
+ </DataCollector>
+ </DataCollectors>
+ </DataCollectionRunSettings>
+</RunSettings> \ No newline at end of file