Merge branch 'master' into renovate/dotnet-monorepo
This commit is contained in:
commit
2d8b375a64
|
@ -35,14 +35,14 @@ jobs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Install ABI CompatibilityChecker Tool'
|
displayName: 'Install ABI CompatibilityChecker Tool'
|
||||||
inputs:
|
inputs:
|
||||||
command: custom
|
command: custom
|
||||||
custom: tool
|
custom: tool
|
||||||
arguments: 'update compatibilitychecker -g'
|
arguments: 'update compatibilitychecker -g'
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2.198.0
|
||||||
displayName: 'Download New Assembly Build Artifact'
|
displayName: 'Download New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
source: 'current'
|
source: 'current'
|
||||||
|
@ -50,7 +50,7 @@ jobs:
|
||||||
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
path: "$(System.ArtifactsDirectory)/new-artifacts"
|
||||||
runVersion: "latest"
|
runVersion: "latest"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2.211.0
|
||||||
displayName: 'Copy New Assembly Build Artifact'
|
displayName: 'Copy New Assembly Build Artifact'
|
||||||
inputs:
|
inputs:
|
||||||
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
|
||||||
|
@ -60,7 +60,7 @@ jobs:
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2.198.0
|
||||||
displayName: 'Download Reference Assembly Build Artifact'
|
displayName: 'Download Reference Assembly Build Artifact'
|
||||||
enabled: false
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -72,7 +72,7 @@ jobs:
|
||||||
runVersion: "latestFromBranch"
|
runVersion: "latestFromBranch"
|
||||||
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
|
||||||
|
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2.211.0
|
||||||
displayName: 'Copy Reference Assembly Build Artifact'
|
displayName: 'Copy Reference Assembly Build Artifact'
|
||||||
enabled: false
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -83,7 +83,7 @@ jobs:
|
||||||
overWrite: true
|
overWrite: true
|
||||||
flattenFolders: true
|
flattenFolders: true
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Execute ABI Compatibility Check Tool'
|
displayName: 'Execute ABI Compatibility Check Tool'
|
||||||
enabled: false
|
enabled: false
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
submodules: true
|
submodules: true
|
||||||
persistCredentials: true
|
persistCredentials: true
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2.198.0
|
||||||
displayName: 'Download Web Branch'
|
displayName: 'Download Web Branch'
|
||||||
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -31,7 +31,7 @@ jobs:
|
||||||
pipeline: 'Jellyfin Web'
|
pipeline: 'Jellyfin Web'
|
||||||
runBranch: variables['Build.SourceBranch']
|
runBranch: variables['Build.SourceBranch']
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2.198.0
|
||||||
displayName: 'Download Web Target'
|
displayName: 'Download Web Target'
|
||||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -42,7 +42,7 @@ jobs:
|
||||||
pipeline: 'Jellyfin Web'
|
pipeline: 'Jellyfin Web'
|
||||||
runBranch: variables['System.PullRequest.TargetBranch']
|
runBranch: variables['System.PullRequest.TargetBranch']
|
||||||
|
|
||||||
- task: ExtractFiles@1
|
- task: ExtractFiles@1.211.0
|
||||||
displayName: 'Extract Web Client'
|
displayName: 'Extract Web Client'
|
||||||
inputs:
|
inputs:
|
||||||
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
|
||||||
|
@ -55,7 +55,7 @@ jobs:
|
||||||
packageType: sdk
|
packageType: sdk
|
||||||
version: ${{ parameters.DotNetSdkVersion }}
|
version: ${{ parameters.DotNetSdkVersion }}
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Publish Server'
|
displayName: 'Publish Server'
|
||||||
inputs:
|
inputs:
|
||||||
command: publish
|
command: publish
|
||||||
|
|
|
@ -69,7 +69,7 @@ jobs:
|
||||||
runOptions: 'inline'
|
runOptions: 'inline'
|
||||||
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
|
||||||
|
|
||||||
- task: CopyFilesOverSSH@0
|
- task: CopyFilesOverSSH@0.212.0
|
||||||
displayName: 'Upload artifacts to repository server'
|
displayName: 'Upload artifacts to repository server'
|
||||||
inputs:
|
inputs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
displayName: Set release version (stable)
|
displayName: Set release version (stable)
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
|
||||||
- task: DownloadPipelineArtifact@2
|
- task: DownloadPipelineArtifact@2.198.0
|
||||||
displayName: 'Download OpenAPI Spec'
|
displayName: 'Download OpenAPI Spec'
|
||||||
inputs:
|
inputs:
|
||||||
source: 'current'
|
source: 'current'
|
||||||
|
@ -105,7 +105,7 @@ jobs:
|
||||||
runOptions: 'inline'
|
runOptions: 'inline'
|
||||||
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
|
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
|
||||||
|
|
||||||
- task: CopyFilesOverSSH@0
|
- task: CopyFilesOverSSH@0.212.0
|
||||||
displayName: 'Upload artifacts to repository server'
|
displayName: 'Upload artifacts to repository server'
|
||||||
inputs:
|
inputs:
|
||||||
sshEndpoint: repository
|
sshEndpoint: repository
|
||||||
|
@ -137,7 +137,7 @@ jobs:
|
||||||
displayName: Set release version (stable)
|
displayName: Set release version (stable)
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
|
|
||||||
- task: Docker@2
|
- task: Docker@2.211.0
|
||||||
displayName: 'Push Unstable Image'
|
displayName: 'Push Unstable Image'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -150,7 +150,7 @@ jobs:
|
||||||
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
|
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
|
||||||
unstable-$(BuildConfiguration)
|
unstable-$(BuildConfiguration)
|
||||||
|
|
||||||
- task: Docker@2
|
- task: Docker@2.211.0
|
||||||
displayName: 'Push Stable Image'
|
displayName: 'Push Stable Image'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -210,7 +210,7 @@ jobs:
|
||||||
packageType: 'sdk'
|
packageType: 'sdk'
|
||||||
version: '6.0.x'
|
version: '6.0.x'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Build Stable Nuget packages'
|
displayName: 'Build Stable Nuget packages'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -225,7 +225,7 @@ jobs:
|
||||||
custom: 'pack'
|
custom: 'pack'
|
||||||
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
|
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Build Unstable Nuget packages'
|
displayName: 'Build Unstable Nuget packages'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -256,7 +256,7 @@ jobs:
|
||||||
publishFeedCredentials: 'NugetOrg'
|
publishFeedCredentials: 'NugetOrg'
|
||||||
allowPackageConflicts: true # This ignores an error if the version already exists
|
allowPackageConflicts: true # This ignores an error if the version already exists
|
||||||
|
|
||||||
- task: NuGetAuthenticate@0
|
- task: NuGetAuthenticate@0.203.0
|
||||||
displayName: 'Authenticate to unstable Nuget feed'
|
displayName: 'Authenticate to unstable Nuget feed'
|
||||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ jobs:
|
||||||
organization: 'jellyfin'
|
organization: 'jellyfin'
|
||||||
projectKey: 'jellyfin_jellyfin'
|
projectKey: 'jellyfin_jellyfin'
|
||||||
|
|
||||||
- task: DotNetCoreCLI@2
|
- task: DotNetCoreCLI@2.210.0
|
||||||
displayName: 'Run CLI Tests'
|
displayName: 'Run CLI Tests'
|
||||||
inputs:
|
inputs:
|
||||||
command: "test"
|
command: "test"
|
||||||
|
|
12
.github/workflows/automation.yml
vendored
12
.github/workflows/automation.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Apply label
|
- name: Apply label
|
||||||
uses: eps1lon/actions-label-merge-conflict@v2.0.1
|
uses: eps1lon/actions-label-merge-conflict@b8bf8341285ec9a4567d4318ba474fee998a6919 # tag=v2.0.1
|
||||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
||||||
with:
|
with:
|
||||||
dirtyLabel: 'merge conflict'
|
dirtyLabel: 'merge conflict'
|
||||||
|
@ -26,7 +26,7 @@ jobs:
|
||||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Remove from 'Current Release' project
|
- name: Remove from 'Current Release' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add to 'Release Next' project
|
- name: Add to 'Release Next' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
if: (github.event.pull_request || github.event.issue.pull_request) && github.event.action == 'opened'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
@ -44,7 +44,7 @@ jobs:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add to 'Current Release' project
|
- name: Add to 'Current Release' project
|
||||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||||
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
if: (github.event.pull_request || github.event.issue.pull_request) && !contains(github.event.*.labels.*.name, 'stable backport')
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
@ -58,7 +58,7 @@ jobs:
|
||||||
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
run: echo "::set-output name=number::$(curl -s ${{ github.event.issue.comments_url }} | jq '.[] | select(.author_association == "MEMBER") | .author_association' | wc -l)"
|
||||||
|
|
||||||
- name: Move issue to needs triage
|
- name: Move issue to needs triage
|
||||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||||
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' && steps.member_comments.outputs.number <= 1
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
@ -67,7 +67,7 @@ jobs:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
- name: Add issue to triage project
|
- name: Add issue to triage project
|
||||||
uses: alex-page/github-project-automation-plus@v0.8.1
|
uses: alex-page/github-project-automation-plus@1f8873e97e3c8f58161a323b7c568c1f623a1c4d # tag=v0.8.2
|
||||||
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
if: github.event.issue.pull_request == '' && github.event.action == 'opened'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
|
|
10
.github/workflows/codeql-analysis.yml
vendored
10
.github/workflows/codeql-analysis.yml
vendored
|
@ -20,18 +20,18 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v2
|
uses: github/codeql-action/init@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@v2
|
uses: github/codeql-action/autobuild@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@v2
|
uses: github/codeql-action/analyze@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
|
||||||
|
|
16
.github/workflows/commands.yml
vendored
16
.github/workflows/commands.yml
vendored
|
@ -16,20 +16,20 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify as seen
|
- name: Notify as seen
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Automatic Rebase
|
- name: Automatic Rebase
|
||||||
uses: cirrus-actions/rebase@1.7
|
uses: cirrus-actions/rebase@6e572f08c244e2f04f9beb85a943eb618218714d # tag=1.7
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify as seen
|
- name: Notify as seen
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ github.event.comment != null }}
|
if: ${{ github.event.comment != null }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -47,14 +47,14 @@ jobs:
|
||||||
reactions: eyes
|
reactions: eyes
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Notify as running
|
- name: Notify as running
|
||||||
id: comment_running
|
id: comment_running
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ github.event.comment != null }}
|
if: ${{ github.event.comment != null }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -89,7 +89,7 @@ jobs:
|
||||||
exit ${retcode}
|
exit ${retcode}
|
||||||
|
|
||||||
- name: Notify with result success
|
- name: Notify with result success
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ github.event.comment != null && success() }}
|
if: ${{ github.event.comment != null && success() }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -104,7 +104,7 @@ jobs:
|
||||||
reactions: hooray
|
reactions: hooray
|
||||||
|
|
||||||
- name: Notify with result failure
|
- name: Notify with result failure
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ github.event.comment != null && failure() }}
|
if: ${{ github.event.comment != null && failure() }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
22
.github/workflows/openapi.yml
vendored
22
.github/workflows/openapi.yml
vendored
|
@ -12,18 +12,18 @@ jobs:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
@ -37,17 +37,17 @@ jobs:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ github.base_ref }}
|
||||||
- name: Setup .NET Core
|
- name: Setup .NET Core
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: '6.0.x'
|
dotnet-version: '6.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
|
@ -63,12 +63,12 @@ jobs:
|
||||||
- openapi-base
|
- openapi-base
|
||||||
steps:
|
steps:
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v3
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
- name: Download openapi-base
|
- name: Download openapi-base
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v3
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
path: openapi-base
|
path: openapi-base
|
||||||
|
@ -90,14 +90,14 @@ jobs:
|
||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
echo ::set-output name=body::$body
|
echo ::set-output name=body::$body
|
||||||
- name: Find difference comment
|
- name: Find difference comment
|
||||||
uses: peter-evans/find-comment@v2
|
uses: peter-evans/find-comment@b657a70ff16d17651703a84bee1cb9ad9d2be2ea # tag=v2
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
direction: last
|
direction: last
|
||||||
body-includes: openapi-diff-workflow-comment
|
body-includes: openapi-diff-workflow-comment
|
||||||
- name: Reply or edit difference comment (changed)
|
- name: Reply or edit difference comment (changed)
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ steps.read-diff.outputs.body != '' }}
|
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
@ -112,7 +112,7 @@ jobs:
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
- name: Edit difference comment (unchanged)
|
- name: Edit difference comment (unchanged)
|
||||||
uses: peter-evans/create-or-update-comment@v2
|
uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
|
||||||
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
|
2
.github/workflows/repo-stale.yaml
vendored
2
.github/workflows/repo-stale.yaml
vendored
|
@ -10,7 +10,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@v6
|
- uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
days-before-stale: 120
|
days-before-stale: 120
|
||||||
|
|
|
@ -1088,15 +1088,7 @@ namespace Emby.Server.Implementations
|
||||||
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Published server ends with a /
|
return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
|
||||||
if (!string.IsNullOrEmpty(PublishedServerUrl))
|
|
||||||
{
|
|
||||||
// Published server ends with a '/', so we need to remove it.
|
|
||||||
return PublishedServerUrl.Trim('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
string smart = NetManager.GetBindInterface(request, out var port);
|
|
||||||
return GetLocalApiUrl(smart.Trim('/'), request.Scheme, port);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.10" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.10" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
<PackageReference Include="Mono.Nat" Version="3.0.4" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.3.0" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.3.0" />
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
"TasksChannelsCategory": "قنوات الإنترنت",
|
"TasksChannelsCategory": "قنوات الإنترنت",
|
||||||
"TasksLibraryCategory": "مكتبة",
|
"TasksLibraryCategory": "مكتبة",
|
||||||
"TasksMaintenanceCategory": "صيانة",
|
"TasksMaintenanceCategory": "صيانة",
|
||||||
"TaskRefreshLibraryDescription": "يفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
|
"TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
|
||||||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||||
"TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
|
"TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
|
||||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimalizovat databázi",
|
"TaskOptimizeDatabase": "Optimalizovat databázi",
|
||||||
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
||||||
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
||||||
"External": "Externí"
|
"External": "Externí",
|
||||||
|
"HearingImpaired": "Sluchově postižení"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimise database",
|
"TaskOptimizeDatabase": "Optimise database",
|
||||||
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
||||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||||
"External": "External"
|
"External": "External",
|
||||||
|
"HearingImpaired": "Hearing Impaired"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimización de base de datos",
|
"TaskOptimizeDatabase": "Optimización de base de datos",
|
||||||
"External": "Externo",
|
"External": "Externo",
|
||||||
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.",
|
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reprodución HLS más precisas. Esta tarea puede durar mucho tiempo.",
|
||||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
|
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
||||||
|
"HearingImpaired": "Personas con discapacidad auditiva"
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,5 +116,12 @@
|
||||||
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
|
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
|
||||||
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
|
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
|
||||||
"Application": "Aplikazioa",
|
"Application": "Aplikazioa",
|
||||||
"AppDeviceValues": "App: {0}, Gailua: {1}"
|
"AppDeviceValues": "App: {0}, Gailua: {1}",
|
||||||
|
"HearingImpaired": "Entzunaldia aldatua",
|
||||||
|
"ProviderValue": "Hornitzailea: {0}",
|
||||||
|
"TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.",
|
||||||
|
"HeaderRecordingGroups": "Grabaketa taldeak",
|
||||||
|
"Inherit": "Oinordetu",
|
||||||
|
"TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.",
|
||||||
|
"TaskKeyframeExtractor": "Fotograma gakoen erauzgailua"
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,5 +122,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimoi tietokanta",
|
"TaskOptimizeDatabase": "Optimoi tietokanta",
|
||||||
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
||||||
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
||||||
"External": "Ulkoinen"
|
"External": "Ulkoinen",
|
||||||
|
"HearingImpaired": "Kuulorajoitteinen"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||||
"Books": "Livres",
|
"Books": "Livres",
|
||||||
"CameraImageUploadedFrom": "Une nouvelle image de caméra a été téléchargée depuis {0}",
|
"CameraImageUploadedFrom": "Une nouvelle photo a été téléversée depuis {0}",
|
||||||
"Channels": "Chaînes",
|
"Channels": "Chaînes",
|
||||||
"ChapterNameValue": "Chapitre {0}",
|
"ChapterNameValue": "Chapitre {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimiser la base de données",
|
"TaskOptimizeDatabase": "Optimiser la base de données",
|
||||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||||
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
||||||
"External": "Externe"
|
"External": "Externe",
|
||||||
|
"HearingImpaired": "Malentendants"
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
"HeaderFavoriteEpisodes": "Episodios Favoritos",
|
"HeaderFavoriteEpisodes": "Episodios Favoritos",
|
||||||
"HeaderFavoriteArtists": "Artistas Favoritos",
|
"HeaderFavoriteArtists": "Artistas Favoritos",
|
||||||
"HeaderFavoriteAlbums": "Álbunes Favoritos",
|
"HeaderFavoriteAlbums": "Álbunes Favoritos",
|
||||||
"HeaderContinueWatching": "Seguir mirando",
|
"HeaderContinueWatching": "Seguir vendo",
|
||||||
"HeaderAlbumArtists": "Artistas do Album",
|
"HeaderAlbumArtists": "Artistas do Album",
|
||||||
"Genres": "Xéneros",
|
"Genres": "Xéneros",
|
||||||
"Forced": "Forzado",
|
"Forced": "Forzado",
|
||||||
|
@ -119,5 +119,9 @@
|
||||||
"UserOnlineFromDevice": "{0} está en liña desde {1}",
|
"UserOnlineFromDevice": "{0} está en liña desde {1}",
|
||||||
"UserOfflineFromDevice": "{0} desconectouse desde {1}",
|
"UserOfflineFromDevice": "{0} desconectouse desde {1}",
|
||||||
"TaskOptimizeDatabaseDescription": "Compacta e libera o espazo libre da base de datos. Executar esta tarefa logo de realizar mudanzas que impliquen modificacións da base de datos ou despois de escanear a biblioteca pode traer mellorías de desempeño.",
|
"TaskOptimizeDatabaseDescription": "Compacta e libera o espazo libre da base de datos. Executar esta tarefa logo de realizar mudanzas que impliquen modificacións da base de datos ou despois de escanear a biblioteca pode traer mellorías de desempeño.",
|
||||||
"TaskOptimizeDatabase": "Optimizar base de datos"
|
"TaskOptimizeDatabase": "Optimizar base de datos",
|
||||||
|
"TaskKeyframeExtractorDescription": "Extrae fragmentos do vídeo para crear listas de reprodución HLS máis precisas. Podería levarlle bastante tempo.",
|
||||||
|
"External": "Externo",
|
||||||
|
"HearingImpaired": "Problemas de audición",
|
||||||
|
"TaskKeyframeExtractor": "Extractor de fragmentos"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.",
|
"TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.",
|
||||||
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
|
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
|
||||||
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
|
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
|
||||||
"External": "חיצוני"
|
"External": "חיצוני",
|
||||||
|
"HearingImpaired": "לקוי שמיעה"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Adatbázis optimalizálása",
|
"TaskOptimizeDatabase": "Adatbázis optimalizálása",
|
||||||
"TaskKeyframeExtractor": "Kulcskockák kibontása",
|
"TaskKeyframeExtractor": "Kulcskockák kibontása",
|
||||||
"TaskKeyframeExtractorDescription": "Kulcskockákat bont ki a videofájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
"TaskKeyframeExtractorDescription": "Kulcskockákat bont ki a videofájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
||||||
"External": "Külső"
|
"External": "Külső",
|
||||||
|
"HearingImpaired": "Hallássérült"
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,5 +122,6 @@
|
||||||
"TaskOptimizeDatabase": "Optimalkan basis data",
|
"TaskOptimizeDatabase": "Optimalkan basis data",
|
||||||
"TaskKeyframeExtractorDescription": "Ekstrak bingkai utama dari file video untuk membuat daftar putar HLS yang lebih tepat. Tugas ini dapat berjalan untuk waktu yang lama.",
|
"TaskKeyframeExtractorDescription": "Ekstrak bingkai utama dari file video untuk membuat daftar putar HLS yang lebih tepat. Tugas ini dapat berjalan untuk waktu yang lama.",
|
||||||
"TaskKeyframeExtractor": "Ekstraktor Bingkai Utama",
|
"TaskKeyframeExtractor": "Ekstraktor Bingkai Utama",
|
||||||
"External": "Luar"
|
"External": "Luar",
|
||||||
|
"HearingImpaired": "Gangguan Pendengaran"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
{}
|
{
|
||||||
|
"Albums": "lo albuma",
|
||||||
|
"Artists": "lo larpra",
|
||||||
|
"Books": "lo cukta",
|
||||||
|
"HeaderAlbumArtists": "lo albuma larpra",
|
||||||
|
"Playlists": "lo zgipor"
|
||||||
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "Otimizar base de dados",
|
"TaskOptimizeDatabase": "Otimizar base de dados",
|
||||||
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
||||||
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.",
|
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de arquivos de vídeo para criar listas de reprodução HLS mais precisas. Esta tarefa pode ser executada por um longo tempo.",
|
||||||
"External": "Externo"
|
"External": "Externo",
|
||||||
|
"HearingImpaired": "Deficiência Auditiva"
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,5 +119,9 @@
|
||||||
"Forced": "I detyruar",
|
"Forced": "I detyruar",
|
||||||
"Default": "Parazgjedhur",
|
"Default": "Parazgjedhur",
|
||||||
"TaskOptimizeDatabaseDescription": "Kompakton bazën e të dhënave dhe shkurton hapësirën e lirë. Drejtimi i kësaj detyre pasi skanoni bibliotekën ose bëni ndryshime të tjera që nënkuptojnë modifikime të bazës së të dhënave mund të përmirësojë performancën.",
|
"TaskOptimizeDatabaseDescription": "Kompakton bazën e të dhënave dhe shkurton hapësirën e lirë. Drejtimi i kësaj detyre pasi skanoni bibliotekën ose bëni ndryshime të tjera që nënkuptojnë modifikime të bazës së të dhënave mund të përmirësojë performancën.",
|
||||||
"TaskOptimizeDatabase": "Optimizo databazën"
|
"TaskOptimizeDatabase": "Optimizo databazën",
|
||||||
|
"TaskKeyframeExtractorDescription": "Nxjerrë kornizat kryesore nga skedarët video për të krijuar lista luajtjeje më të sakta HLS. Ky veprim mund të dojë një kohë të gjatë për tu kompletuar.",
|
||||||
|
"TaskKeyframeExtractor": "Nxjerrës i kornizës kryesore",
|
||||||
|
"External": "Jashtem",
|
||||||
|
"HearingImpaired": "Dëgjimi i dëmtuar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,5 +122,6 @@
|
||||||
"TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.",
|
"TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.",
|
||||||
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
|
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
|
||||||
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
|
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
|
||||||
"External": "Зовнішній"
|
"External": "Зовнішній",
|
||||||
|
"HearingImpaired": "З порушеннями слуху"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"TaskOptimizeDatabase": "优化数据库",
|
"TaskOptimizeDatabase": "优化数据库",
|
||||||
"TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。",
|
"TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。",
|
||||||
"TaskKeyframeExtractor": "关键帧提取器",
|
"TaskKeyframeExtractor": "关键帧提取器",
|
||||||
"External": "外部"
|
"External": "外部",
|
||||||
|
"HearingImpaired": "听力障碍"
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="LrcParser" Version="2022.529.1" />
|
<PackageReference Include="LrcParser" Version="2022.529.1" />
|
||||||
|
<PackageReference Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
|
||||||
|
|
|
@ -1,37 +1,52 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
|
using MetaBrainz.MusicBrainz;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz plugin configuration.
|
||||||
|
/// </summary>
|
||||||
|
public class PluginConfiguration : BasePluginConfiguration
|
||||||
{
|
{
|
||||||
public class PluginConfiguration : BasePluginConfiguration
|
private const string DefaultServer = "musicbrainz.org";
|
||||||
|
|
||||||
|
private const double DefaultRateLimit = 1.0;
|
||||||
|
|
||||||
|
private string _server = DefaultServer;
|
||||||
|
|
||||||
|
private double _rateLimit = DefaultRateLimit;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the server url.
|
||||||
|
/// </summary>
|
||||||
|
public string Server
|
||||||
{
|
{
|
||||||
private string _server = Plugin.DefaultServer;
|
get => _server;
|
||||||
|
|
||||||
private long _rateLimit = Plugin.DefaultRateLimit;
|
set => _server = value.TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
public string Server
|
/// <summary>
|
||||||
|
/// Gets or sets the rate limit.
|
||||||
|
/// </summary>
|
||||||
|
public double RateLimit
|
||||||
|
{
|
||||||
|
get => _rateLimit;
|
||||||
|
set
|
||||||
{
|
{
|
||||||
get => _server;
|
if (value < DefaultRateLimit && _server == DefaultServer)
|
||||||
set => _server = value.TrimEnd('/');
|
|
||||||
}
|
|
||||||
|
|
||||||
public long RateLimit
|
|
||||||
{
|
|
||||||
get => _rateLimit;
|
|
||||||
set
|
|
||||||
{
|
{
|
||||||
if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
|
_rateLimit = DefaultRateLimit;
|
||||||
{
|
}
|
||||||
_rateLimit = Plugin.DefaultRateLimit;
|
else
|
||||||
}
|
{
|
||||||
else
|
_rateLimit = value;
|
||||||
{
|
|
||||||
_rateLimit = value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ReplaceArtistName { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to replace the artist name.
|
||||||
|
/// </summary>
|
||||||
|
public bool ReplaceArtistName { get; set; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz album artist external id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
|
public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz album external id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzAlbumExternalId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzAlbumExternalId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
|
public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz artist external id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzArtistExternalId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzArtistExternalId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
public bool Supports(IHasProviderIds item) => item is MusicArtist;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
|
@ -18,257 +10,152 @@ using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
using MediaBrowser.Providers.Music;
|
||||||
|
using MetaBrainz.MusicBrainz;
|
||||||
|
using MetaBrainz.MusicBrainz.Interfaces.Entities;
|
||||||
|
using MetaBrainz.MusicBrainz.Interfaces.Searches;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz artist provider.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>, IDisposable
|
||||||
{
|
{
|
||||||
public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, ArtistInfo>
|
private readonly Query _musicBrainzQuery;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MusicBrainzArtistProvider"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public MusicBrainzArtistProvider()
|
||||||
{
|
{
|
||||||
public string Name => "MusicBrainz";
|
MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
var musicBrainzId = searchInfo.GetMusicBrainzArtistId();
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
|
||||||
{
|
{
|
||||||
var url = "/ws/2/artist/?query=arid:{0}" + musicBrainzId.ToString(CultureInfo.InvariantCulture);
|
Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server;
|
||||||
|
Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
|
||||||
using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
return GetResultsFromResponse(stream);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// They seem to throw bad request failures on any term with a slash
|
|
||||||
var nameToSearch = searchInfo.Name.Replace('/', ' ');
|
|
||||||
|
|
||||||
var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
|
|
||||||
|
|
||||||
using (var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false))
|
|
||||||
await using (var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false))
|
|
||||||
{
|
|
||||||
var results = GetResultsFromResponse(stream).ToList();
|
|
||||||
|
|
||||||
if (results.Count > 0)
|
|
||||||
{
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchInfo.Name.HasDiacritics())
|
|
||||||
{
|
|
||||||
// Try again using the search with accent characters url
|
|
||||||
url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=artistaccent:\"{0}\"", UrlEncode(nameToSearch));
|
|
||||||
|
|
||||||
using var response = await MusicBrainzAlbumProvider.Current.GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
return GetResultsFromResponse(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Enumerable.Empty<RemoteSearchResult>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
|
|
||||||
{
|
|
||||||
using var oReader = new StreamReader(stream, Encoding.UTF8);
|
|
||||||
var settings = new XmlReaderSettings()
|
|
||||||
{
|
|
||||||
ValidationType = ValidationType.None,
|
|
||||||
CheckCharacters = false,
|
|
||||||
IgnoreProcessingInstructions = true,
|
|
||||||
IgnoreComments = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
using var reader = XmlReader.Create(oReader, settings);
|
_musicBrainzQuery = new Query();
|
||||||
reader.MoveToContent();
|
}
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
// Loop through each element
|
/// <inheritdoc />
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
public string Name => "MusicBrainz";
|
||||||
{
|
|
||||||
if (reader.NodeType == XmlNodeType.Element)
|
|
||||||
{
|
|
||||||
switch (reader.Name)
|
|
||||||
{
|
|
||||||
case "artist-list":
|
|
||||||
{
|
|
||||||
if (reader.IsEmptyElement)
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var subReader = reader.ReadSubtree();
|
/// <inheritdoc />
|
||||||
return ParseArtistList(subReader).ToList();
|
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
|
||||||
}
|
{
|
||||||
|
var artistId = searchInfo.GetMusicBrainzArtistId();
|
||||||
|
|
||||||
default:
|
if (!string.IsNullOrWhiteSpace(artistId))
|
||||||
{
|
{
|
||||||
reader.Skip();
|
var artistResult = await _musicBrainzQuery.LookupArtistAsync(new Guid(artistId), Include.Aliases, null, null, cancellationToken).ConfigureAwait(false);
|
||||||
break;
|
return GetResultFromResponse(artistResult).SingleItemAsEnumerable();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Enumerable.Empty<RemoteSearchResult>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
|
var artistSearchResults = await _musicBrainzQuery.FindArtistsAsync($"\"{searchInfo.Name}\"", null, null, false, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (artistSearchResults.Results.Count > 0)
|
||||||
{
|
{
|
||||||
reader.MoveToContent();
|
return GetResultsFromResponse(artistSearchResults.Results);
|
||||||
reader.Read();
|
}
|
||||||
|
|
||||||
// Loop through each element
|
if (searchInfo.Name.HasDiacritics())
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
{
|
||||||
|
// Try again using the search with an accented characters query
|
||||||
|
var artistAccentsSearchResults = await _musicBrainzQuery.FindArtistsAsync($"artistaccent:\"{searchInfo.Name}\"", null, null, false, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
if (artistAccentsSearchResults.Results.Count > 0)
|
||||||
{
|
{
|
||||||
if (reader.NodeType == XmlNodeType.Element)
|
return GetResultsFromResponse(artistAccentsSearchResults.Results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enumerable.Empty<RemoteSearchResult>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(IEnumerable<ISearchResult<IArtist>>? releaseSearchResults)
|
||||||
|
{
|
||||||
|
if (releaseSearchResults is null)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var result in releaseSearchResults)
|
||||||
|
{
|
||||||
|
yield return GetResultFromResponse(result.Item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RemoteSearchResult GetResultFromResponse(IArtist artist)
|
||||||
|
{
|
||||||
|
var searchResult = new RemoteSearchResult
|
||||||
|
{
|
||||||
|
Name = artist.Name,
|
||||||
|
ProductionYear = artist.LifeSpan?.Begin?.Year,
|
||||||
|
PremiereDate = artist.LifeSpan?.Begin?.NearestDate
|
||||||
|
};
|
||||||
|
|
||||||
|
searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
|
||||||
|
|
||||||
|
return searchResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var result = new MetadataResult<MusicArtist> { Item = new MusicArtist() };
|
||||||
|
|
||||||
|
var musicBrainzId = info.GetMusicBrainzArtistId();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(musicBrainzId))
|
||||||
|
{
|
||||||
|
var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var singleResult = searchResults.FirstOrDefault();
|
||||||
|
|
||||||
|
if (singleResult != null)
|
||||||
|
{
|
||||||
|
musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
|
||||||
|
result.Item.Overview = singleResult.Overview;
|
||||||
|
|
||||||
|
if (Plugin.Instance!.Configuration.ReplaceArtistName)
|
||||||
{
|
{
|
||||||
switch (reader.Name)
|
result.Item.Name = singleResult.Name;
|
||||||
{
|
|
||||||
case "artist":
|
|
||||||
{
|
|
||||||
if (reader.IsEmptyElement)
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mbzId = reader.GetAttribute("id");
|
|
||||||
|
|
||||||
using var subReader = reader.ReadSubtree();
|
|
||||||
var artist = ParseArtist(subReader, mbzId);
|
|
||||||
if (artist != null)
|
|
||||||
{
|
|
||||||
yield return artist;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
reader.Skip();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private RemoteSearchResult ParseArtist(XmlReader reader, string artistId)
|
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
||||||
{
|
{
|
||||||
var result = new RemoteSearchResult();
|
result.HasMetadata = true;
|
||||||
|
result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
|
||||||
reader.MoveToContent();
|
|
||||||
reader.Read();
|
|
||||||
|
|
||||||
// http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
|
|
||||||
|
|
||||||
// Loop through each element
|
|
||||||
while (!reader.EOF && reader.ReadState == ReadState.Interactive)
|
|
||||||
{
|
|
||||||
if (reader.NodeType == XmlNodeType.Element)
|
|
||||||
{
|
|
||||||
switch (reader.Name)
|
|
||||||
{
|
|
||||||
case "name":
|
|
||||||
{
|
|
||||||
result.Name = reader.ReadElementContentAsString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case "annotation":
|
|
||||||
{
|
|
||||||
result.Overview = reader.ReadElementContentAsString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
// there is sort-name if ever needed
|
|
||||||
reader.Skip();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reader.Read();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId);
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
return result;
|
||||||
public async Task<MetadataResult<MusicArtist>> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose all resources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">Whether to dispose.</param>
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
{
|
{
|
||||||
var result = new MetadataResult<MusicArtist>
|
_musicBrainzQuery.Dispose();
|
||||||
{
|
|
||||||
Item = new MusicArtist()
|
|
||||||
};
|
|
||||||
|
|
||||||
var musicBrainzId = info.GetMusicBrainzArtistId();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(musicBrainzId))
|
|
||||||
{
|
|
||||||
var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
var singleResult = searchResults.FirstOrDefault();
|
|
||||||
|
|
||||||
if (singleResult != null)
|
|
||||||
{
|
|
||||||
musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
|
|
||||||
result.Item.Overview = singleResult.Overview;
|
|
||||||
|
|
||||||
if (Plugin.Instance.Configuration.ReplaceArtistName)
|
|
||||||
{
|
|
||||||
result.Item.Name = singleResult.Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(musicBrainzId))
|
|
||||||
{
|
|
||||||
result.HasMetadata = true;
|
|
||||||
result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Encodes an URL.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="name">The name.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
|
||||||
private static string UrlEncode(string name)
|
|
||||||
{
|
|
||||||
return WebUtility.UrlEncode(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<HttpResponseMessage> GetImageResponse(string url, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz other artist external id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzOtherArtistExternalId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzOtherArtistExternalId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz release group external id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
|
public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
|
public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Music
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MusicBrainz track id.
|
||||||
|
/// </summary>
|
||||||
|
public class MusicBrainzTrackId : IExternalId
|
||||||
{
|
{
|
||||||
public class MusicBrainzTrackId : IExternalId
|
/// <inheritdoc />
|
||||||
{
|
public string ProviderName => "MusicBrainz";
|
||||||
/// <inheritdoc />
|
|
||||||
public string ProviderName => "MusicBrainz";
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Key => MetadataProvider.MusicBrainzTrack.ToString();
|
public string Key => MetadataProvider.MusicBrainzTrack.ToString();
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
|
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
|
public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}";
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +1,64 @@
|
||||||
#nullable disable
|
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
|
||||||
|
using MetaBrainz.MusicBrainz;
|
||||||
|
|
||||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz
|
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Plugin instance.
|
||||||
|
/// </summary>
|
||||||
|
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||||
{
|
{
|
||||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||||
|
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||||
|
/// <param name="applicationHost">Instance of the <see cref="IApplicationHost"/> interface.</param>
|
||||||
|
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IApplicationHost applicationHost)
|
||||||
|
: base(applicationPaths, xmlSerializer)
|
||||||
{
|
{
|
||||||
public const string DefaultServer = "https://musicbrainz.org";
|
Instance = this;
|
||||||
|
|
||||||
public const long DefaultRateLimit = 2000u;
|
// TODO: Change this to "JellyfinMusicBrainzPlugin" once we take it out of the server repo.
|
||||||
|
Query.DefaultUserAgent.Add(new ProductInfoHeaderValue(applicationHost.Name.Replace(' ', '-'), applicationHost.ApplicationVersionString));
|
||||||
|
Query.DefaultUserAgent.Add(new ProductInfoHeaderValue($"({applicationHost.ApplicationUserAgentAddress})"));
|
||||||
|
Query.DelayBetweenRequests = Instance.Configuration.RateLimit;
|
||||||
|
Query.DefaultServer = Instance.Configuration.Server;
|
||||||
|
}
|
||||||
|
|
||||||
public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
|
/// <summary>
|
||||||
: base(applicationPaths, xmlSerializer)
|
/// Gets the current plugin instance.
|
||||||
|
/// </summary>
|
||||||
|
public static Plugin? Instance { get; private set; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Name => "MusicBrainz";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public override string Description => "Get artist and album metadata from any MusicBrainz server.";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
// TODO remove when plugin removed from server.
|
||||||
|
public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IEnumerable<PluginPageInfo> GetPages()
|
||||||
|
{
|
||||||
|
yield return new PluginPageInfo
|
||||||
{
|
{
|
||||||
Instance = this;
|
Name = Name,
|
||||||
}
|
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
|
||||||
|
};
|
||||||
public static Plugin Instance { get; private set; }
|
|
||||||
|
|
||||||
public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
|
|
||||||
|
|
||||||
public override string Name => "MusicBrainz";
|
|
||||||
|
|
||||||
public override string Description => "Get artist and album metadata from any MusicBrainz server.";
|
|
||||||
|
|
||||||
// TODO remove when plugin removed from server.
|
|
||||||
public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
|
|
||||||
|
|
||||||
public IEnumerable<PluginPageInfo> GetPages()
|
|
||||||
{
|
|
||||||
yield return new PluginPageInfo
|
|
||||||
{
|
|
||||||
Name = Name,
|
|
||||||
EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
<PackageReference Include="AutoFixture" Version="4.17.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.18.2" />
|
||||||
<PackageReference Include="SharpFuzz" Version="1.6.2" />
|
<PackageReference Include="SharpFuzz" Version="1.6.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -1,42 +1,31 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Jellyfin.Extensions
|
namespace Jellyfin.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static extensions for the <see cref="IEnumerable{T}"/> interface.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumerableExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Static extensions for the <see cref="IEnumerable{T}"/> interface.
|
/// Determines whether the value is contained in the source collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class EnumerableExtensions
|
/// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param>
|
||||||
|
/// <param name="value">The value to look for in the collection.</param>
|
||||||
|
/// <param name="stringComparison">The string comparison.</param>
|
||||||
|
/// <returns>A value indicating whether the value is contained in the collection.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException">The source is null.</exception>
|
||||||
|
public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
|
||||||
{
|
{
|
||||||
/// <summary>
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
/// Determines whether the value is contained in the source collection.
|
|
||||||
/// </summary>
|
if (source is IList<string> list)
|
||||||
/// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param>
|
|
||||||
/// <param name="value">The value to look for in the collection.</param>
|
|
||||||
/// <param name="stringComparison">The string comparison.</param>
|
|
||||||
/// <returns>A value indicating whether the value is contained in the collection.</returns>
|
|
||||||
/// <exception cref="ArgumentNullException">The source is null.</exception>
|
|
||||||
public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
|
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(source);
|
int len = list.Count;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
if (source is IList<string> list)
|
|
||||||
{
|
{
|
||||||
int len = list.Count;
|
if (value.Equals(list[i], stringComparison))
|
||||||
for (int i = 0; i < len; i++)
|
|
||||||
{
|
|
||||||
if (value.Equals(list[i], stringComparison))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (string element in source)
|
|
||||||
{
|
|
||||||
if (value.Equals(element, stringComparison))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -44,5 +33,26 @@ namespace Jellyfin.Extensions
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (string element in source)
|
||||||
|
{
|
||||||
|
if (value.Equals(element, stringComparison))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets an IEnumerable from a single item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to return.</param>
|
||||||
|
/// <typeparam name="T">The type of item.</typeparam>
|
||||||
|
/// <returns>The IEnumerable{T}.</returns>
|
||||||
|
public static IEnumerable<T> SingleItemAsEnumerable<T>(this T item)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Music;
|
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
using MediaBrowser.XbmcMetadata.Parsers;
|
using MediaBrowser.XbmcMetadata.Parsers;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
|
@ -7,7 +7,7 @@ using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Providers.Music;
|
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||||
using MediaBrowser.XbmcMetadata.Parsers;
|
using MediaBrowser.XbmcMetadata.Parsers;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user