From 51dae418b53ad4c6cca3063df929adddbcd57877 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 18 Oct 2022 23:41:43 +0000
Subject: [PATCH 01/30] chore(deps): update dependency copyfiles to v2.211.0
---
.ci/azure-pipelines-abi.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
index cf74a4201b..cb93c226aa 100644
--- a/.ci/azure-pipelines-abi.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -50,7 +50,7 @@ jobs:
path: "$(System.ArtifactsDirectory)/new-artifacts"
runVersion: "latest"
- - task: CopyFiles@2
+ - task: CopyFiles@2.211.0
displayName: 'Copy New Assembly Build Artifact'
inputs:
sourceFolder: $(System.ArtifactsDirectory)/new-artifacts
@@ -72,7 +72,7 @@ jobs:
runVersion: "latestFromBranch"
runBranch: "refs/heads/$(System.PullRequest.TargetBranch)"
- - task: CopyFiles@2
+ - task: CopyFiles@2.211.0
displayName: 'Copy Reference Assembly Build Artifact'
enabled: false
inputs:
From f39c0c9ab8cbad560e08ee34730397007c95e8a8 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 01:04:21 +0000
Subject: [PATCH 02/30] chore(deps): update dependency copyfilesoverssh to
v0.212.0
---
.ci/azure-pipelines-package.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 926d1d3224..f8f2316677 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -69,7 +69,7 @@ jobs:
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
- - task: CopyFilesOverSSH@0
+ - task: CopyFilesOverSSH@0.212.0
displayName: 'Upload artifacts to repository server'
inputs:
sshEndpoint: repository
@@ -105,7 +105,7 @@ jobs:
runOptions: 'inline'
inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)'
- - task: CopyFilesOverSSH@0
+ - task: CopyFilesOverSSH@0.212.0
displayName: 'Upload artifacts to repository server'
inputs:
sshEndpoint: repository
From 10181d542182e199ce592395d9bdf6d6d6aa4273 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 03:47:45 +0000
Subject: [PATCH 03/30] chore(deps): update dependency docker to v2.211.0
---
.ci/azure-pipelines-package.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 926d1d3224..7f221a3f9f 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -137,7 +137,7 @@ jobs:
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- - task: Docker@2
+ - task: Docker@2.211.0
displayName: 'Push Unstable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
@@ -150,7 +150,7 @@ jobs:
unstable-$(Build.BuildNumber)-$(BuildConfiguration)
unstable-$(BuildConfiguration)
- - task: Docker@2
+ - task: Docker@2.211.0
displayName: 'Push Stable Image'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
From 90c3815348c998daddd0329e49b4477e669a90cb Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 03:47:53 +0000
Subject: [PATCH 04/30] chore(deps): update dependency dotnetcorecli to
v2.210.0
---
.ci/azure-pipelines-abi.yml | 4 ++--
.ci/azure-pipelines-main.yml | 2 +-
.ci/azure-pipelines-package.yml | 4 ++--
.ci/azure-pipelines-test.yml | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
index cf74a4201b..d400202527 100644
--- a/.ci/azure-pipelines-abi.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -35,7 +35,7 @@ jobs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Install ABI CompatibilityChecker Tool'
inputs:
command: custom
@@ -83,7 +83,7 @@ jobs:
overWrite: true
flattenFolders: true
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Execute ABI Compatibility Check Tool'
enabled: false
inputs:
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index b7112ba245..875c7a23e6 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -55,7 +55,7 @@ jobs:
packageType: sdk
version: ${{ parameters.DotNetSdkVersion }}
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Publish Server'
inputs:
command: publish
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 926d1d3224..43a4ca0d26 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -210,7 +210,7 @@ jobs:
packageType: 'sdk'
version: '6.0.x'
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Build Stable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs:
@@ -225,7 +225,7 @@ jobs:
custom: 'pack'
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Build Unstable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
diff --git a/.ci/azure-pipelines-test.yml b/.ci/azure-pipelines-test.yml
index cc94dc2c5a..066df89490 100644
--- a/.ci/azure-pipelines-test.yml
+++ b/.ci/azure-pipelines-test.yml
@@ -51,7 +51,7 @@ jobs:
organization: 'jellyfin'
projectKey: 'jellyfin_jellyfin'
- - task: DotNetCoreCLI@2
+ - task: DotNetCoreCLI@2.210.0
displayName: 'Run CLI Tests'
inputs:
command: "test"
From 94635917ca110ebb8ca2307655f55c49309eab32 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 05:53:18 +0000
Subject: [PATCH 05/30] chore(deps): update dependency downloadpipelineartifact
to v2.198.0
---
.ci/azure-pipelines-abi.yml | 4 ++--
.ci/azure-pipelines-main.yml | 4 ++--
.ci/azure-pipelines-package.yml | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/.ci/azure-pipelines-abi.yml b/.ci/azure-pipelines-abi.yml
index cf74a4201b..28b9fae0ca 100644
--- a/.ci/azure-pipelines-abi.yml
+++ b/.ci/azure-pipelines-abi.yml
@@ -42,7 +42,7 @@ jobs:
custom: tool
arguments: 'update compatibilitychecker -g'
- - task: DownloadPipelineArtifact@2
+ - task: DownloadPipelineArtifact@2.198.0
displayName: 'Download New Assembly Build Artifact'
inputs:
source: 'current'
@@ -60,7 +60,7 @@ jobs:
overWrite: true
flattenFolders: true
- - task: DownloadPipelineArtifact@2
+ - task: DownloadPipelineArtifact@2.198.0
displayName: 'Download Reference Assembly Build Artifact'
enabled: false
inputs:
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index b7112ba245..bca217af52 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -20,7 +20,7 @@ jobs:
submodules: true
persistCredentials: true
- - task: DownloadPipelineArtifact@2
+ - task: DownloadPipelineArtifact@2.198.0
displayName: 'Download Web Branch'
condition: in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion')
inputs:
@@ -31,7 +31,7 @@ jobs:
pipeline: 'Jellyfin Web'
runBranch: variables['Build.SourceBranch']
- - task: DownloadPipelineArtifact@2
+ - task: DownloadPipelineArtifact@2.198.0
displayName: 'Download Web Target'
condition: eq(variables['Build.Reason'], 'PullRequest')
inputs:
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 926d1d3224..f28863c806 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -90,7 +90,7 @@ jobs:
displayName: Set release version (stable)
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
- - task: DownloadPipelineArtifact@2
+ - task: DownloadPipelineArtifact@2.198.0
displayName: 'Download OpenAPI Spec'
inputs:
source: 'current'
From 4bb470ea18e1b91513cdc4495b2207f14e4bf608 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 08:22:17 +0000
Subject: [PATCH 06/30] chore(deps): update dependency extractfiles to v1.211.0
---
.ci/azure-pipelines-main.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.ci/azure-pipelines-main.yml b/.ci/azure-pipelines-main.yml
index b7112ba245..8ae5a27e2a 100644
--- a/.ci/azure-pipelines-main.yml
+++ b/.ci/azure-pipelines-main.yml
@@ -42,7 +42,7 @@ jobs:
pipeline: 'Jellyfin Web'
runBranch: variables['System.PullRequest.TargetBranch']
- - task: ExtractFiles@1
+ - task: ExtractFiles@1.211.0
displayName: 'Extract Web Client'
inputs:
archiveFilePatterns: '$(Agent.TempDirectory)/*.zip'
From 62d8369f923e23c6d9be668674bcc523e6b1891b Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 14:25:10 +0200
Subject: [PATCH 07/30] chore(deps): update dependency mono.nat to v3.0.4
(#8580)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
Emby.Server.Implementations/Emby.Server.Implementations.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index b709d1de49..e0f129c3d9 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -30,7 +30,7 @@
-
+
From 30aed0c092984e831f7c396e3c70f4e1ce98c4b6 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 21:49:02 +0200
Subject: [PATCH 08/30] chore(deps): update
alex-page/github-project-automation-plus action to v0.8.2 (#8576)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/automation.yml | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
index 20294843d5..7749433cff 100644
--- a/.github/workflows/automation.yml
+++ b/.github/workflows/automation.yml
@@ -26,7 +26,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- 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')
continue-on-error: true
with:
@@ -35,7 +35,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- 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'
continue-on-error: true
with:
@@ -44,7 +44,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- 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')
continue-on-error: true
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)"
- 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
continue-on-error: true
with:
@@ -67,7 +67,7 @@ jobs:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
- 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'
continue-on-error: true
with:
From fc6ef797d90ab42b1d1d93c8642568511bfc7058 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 19:50:54 +0000
Subject: [PATCH 09/30] chore(deps): update dependency nugetauthenticate to
v0.203.0
---
.ci/azure-pipelines-package.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index 926d1d3224..bb72b41e71 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -256,7 +256,7 @@ jobs:
publishFeedCredentials: 'NugetOrg'
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'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
From 8af07151cee09f05de77f60f28dcff88a338591c Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 22:17:21 +0200
Subject: [PATCH 10/30] chore(deps): pin dependencies (#8572)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
.github/workflows/automation.yml | 2 +-
.github/workflows/codeql-analysis.yml | 10 +++++-----
.github/workflows/commands.yml | 16 ++++++++--------
.github/workflows/openapi.yml | 22 +++++++++++-----------
.github/workflows/repo-stale.yaml | 2 +-
5 files changed, 26 insertions(+), 26 deletions(-)
diff --git a/.github/workflows/automation.yml b/.github/workflows/automation.yml
index 7749433cff..01cd41a085 100644
--- a/.github/workflows/automation.yml
+++ b/.github/workflows/automation.yml
@@ -14,7 +14,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps:
- 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'}}
with:
dirtyLabel: 'merge conflict'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 1dbd7fa367..b551bb5a6e 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -20,18 +20,18 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
- name: Setup .NET Core
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
with:
dotnet-version: '6.0.x'
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
with:
languages: ${{ matrix.language }}
queries: +security-extended
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@cc7986c02bac29104a72998e67239bb5ee2ee110 # tag=v2
diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml
index 23873706d2..d438e7801d 100644
--- a/.github/workflows/commands.yml
+++ b/.github/workflows/commands.yml
@@ -16,20 +16,20 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Notify as seen
- uses: peter-evans/create-or-update-comment@v2
+ uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
with:
token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }}
reactions: '+1'
- name: Checkout the latest code
- uses: actions/checkout@v3
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Automatic Rebase
- uses: cirrus-actions/rebase@1.7
+ uses: cirrus-actions/rebase@6e572f08c244e2f04f9beb85a943eb618218714d # tag=1.7
env:
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
@@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- 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 }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -47,14 +47,14 @@ jobs:
reactions: eyes
- name: Checkout the latest code
- uses: actions/checkout@v3
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
with:
token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0
- name: Notify as 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 }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -89,7 +89,7 @@ jobs:
exit ${retcode}
- 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() }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
@@ -104,7 +104,7 @@ jobs:
reactions: hooray
- 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() }}
with:
token: ${{ secrets.JF_BOT_TOKEN }}
diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml
index ceb4e8cdff..c4300b39ab 100644
--- a/.github/workflows/openapi.yml
+++ b/.github/workflows/openapi.yml
@@ -12,18 +12,18 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET Core
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
with:
name: openapi-head
retention-days: 14
@@ -37,17 +37,17 @@ jobs:
permissions: read-all
steps:
- name: Checkout repository
- uses: actions/checkout@v3
+ uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3
with:
ref: ${{ github.base_ref }}
- name: Setup .NET Core
- uses: actions/setup-dotnet@v3
+ uses: actions/setup-dotnet@4d4a70f4a5b2a5a5329f13be4ac933f2c9206ac0 # tag=v3
with:
dotnet-version: '6.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # tag=v3
with:
name: openapi-base
retention-days: 14
@@ -63,12 +63,12 @@ jobs:
- openapi-base
steps:
- name: Download openapi-head
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v3
with:
name: openapi-head
path: openapi-head
- name: Download openapi-base
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # tag=v3
with:
name: openapi-base
path: openapi-base
@@ -90,14 +90,14 @@ jobs:
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
- name: Find difference comment
- uses: peter-evans/find-comment@v2
+ uses: peter-evans/find-comment@b657a70ff16d17651703a84bee1cb9ad9d2be2ea # tag=v2
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
direction: last
body-includes: openapi-diff-workflow-comment
- name: Reply or edit difference comment (changed)
- uses: peter-evans/create-or-update-comment@v2
+ uses: peter-evans/create-or-update-comment@2b2c85d0bf1b8a7b4e7e344bd5c71dc4b9196e9f # tag=v2
if: ${{ steps.read-diff.outputs.body != '' }}
with:
issue-number: ${{ github.event.pull_request.number }}
@@ -112,7 +112,7 @@ jobs:
- 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 != '' }}
with:
issue-number: ${{ github.event.pull_request.number }}
diff --git a/.github/workflows/repo-stale.yaml b/.github/workflows/repo-stale.yaml
index 2578f82cfe..f7a77f02b1 100644
--- a/.github/workflows/repo-stale.yaml
+++ b/.github/workflows/repo-stale.yaml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- - uses: actions/stale@v6
+ - uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120
From ac0dbd0b40b51753cb0a431f2fbc1c4e5a843aaf Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Wed, 19 Oct 2022 22:48:29 +0200
Subject: [PATCH 11/30] chore(deps): update dependency moq to v4.18.2 (#8583)
---
.../Emby.Server.Implementations.Fuzz.csproj | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
index 7cd98c29ad..81c8f2ba93 100644
--- a/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
+++ b/fuzz/Emby.Server.Implementations.Fuzz/Emby.Server.Implementations.Fuzz.csproj
@@ -18,7 +18,7 @@
-
+
From 64f67d31473aaf7aee85d0848c97d6cfe519f370 Mon Sep 17 00:00:00 2001
From: 0TTA
Date: Tue, 18 Oct 2022 21:05:49 +0000
Subject: [PATCH 12/30] Translated using Weblate (Arabic) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
---
Emby.Server.Implementations/Localization/Core/ar.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 9dc2fe7996..ada3c77301 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -97,7 +97,7 @@
"TasksChannelsCategory": "قنوات الإنترنت",
"TasksLibraryCategory": "مكتبة",
"TasksMaintenanceCategory": "صيانة",
- "TaskRefreshLibraryDescription": "يفصح مكتبة الوسائط الخاصة بك بحثًا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
+ "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يتحدث البيانات الوصفية.",
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
"TaskRefreshChapterImagesDescription": "يُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
"TaskRefreshChapterImages": "استخراج صور الفصل",
From e9e9dce33571a3697b38ffe239dd9ead9d15377c Mon Sep 17 00:00:00 2001
From: bobthebignose
Date: Wed, 19 Oct 2022 16:56:00 +0000
Subject: [PATCH 13/30] Translated using Weblate (French (Canada)) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fr_CA/
---
Emby.Server.Implementations/Localization/Core/fr-CA.json | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Emby.Server.Implementations/Localization/Core/fr-CA.json b/Emby.Server.Implementations/Localization/Core/fr-CA.json
index 24ca8f8611..3ee045d89e 100644
--- a/Emby.Server.Implementations/Localization/Core/fr-CA.json
+++ b/Emby.Server.Implementations/Localization/Core/fr-CA.json
@@ -5,7 +5,7 @@
"Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"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",
"ChapterNameValue": "Chapitre {0}",
"Collections": "Collections",
@@ -123,5 +123,6 @@
"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.",
"TaskKeyframeExtractor": "Extracteur d'image clé",
- "External": "Externe"
+ "External": "Externe",
+ "HearingImpaired": "Malentendants"
}
From bc958c1f03a0998ffa7d398de52ccbdbe0db8edf Mon Sep 17 00:00:00 2001
From: Csaba
Date: Wed, 19 Oct 2022 03:47:01 +0000
Subject: [PATCH 14/30] Translated using Weblate (Hungarian) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
---
Emby.Server.Implementations/Localization/Core/hu.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/hu.json b/Emby.Server.Implementations/Localization/Core/hu.json
index c7f2f9c85e..62d48cebd8 100644
--- a/Emby.Server.Implementations/Localization/Core/hu.json
+++ b/Emby.Server.Implementations/Localization/Core/hu.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "Adatbázis optimalizálá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.",
- "External": "Külső"
+ "External": "Külső",
+ "HearingImpaired": "Hallássérült"
}
From 83cd1451d485e6370a562993af50de1257dd3cb5 Mon Sep 17 00:00:00 2001
From: Kmotyn
Date: Wed, 19 Oct 2022 23:15:13 +0000
Subject: [PATCH 15/30] Translated using Weblate (Portuguese (Brazil))
Translation: Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/pt_BR/
---
Emby.Server.Implementations/Localization/Core/pt-BR.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/pt-BR.json b/Emby.Server.Implementations/Localization/Core/pt-BR.json
index 38a36a7e01..b9b93b7b6f 100644
--- a/Emby.Server.Implementations/Localization/Core/pt-BR.json
+++ b/Emby.Server.Implementations/Localization/Core/pt-BR.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "Otimizar base de dados",
"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.",
- "External": "Externo"
+ "External": "Externo",
+ "HearingImpaired": "Deficiência Auditiva"
}
From c1e3fa3182936942e3e28d5662f557c886e13dd7 Mon Sep 17 00:00:00 2001
From: wolong gl
Date: Thu, 20 Oct 2022 02:13:07 +0000
Subject: [PATCH 16/30] Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
---
Emby.Server.Implementations/Localization/Core/zh-CN.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/zh-CN.json b/Emby.Server.Implementations/Localization/Core/zh-CN.json
index a121fc376d..ccfbeef0ce 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-CN.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-CN.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "优化数据库",
"TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。",
"TaskKeyframeExtractor": "关键帧提取器",
- "External": "外部"
+ "External": "外部",
+ "HearingImpaired": "听力障碍"
}
From dd637620627cd886ee097e4081363558cca41144 Mon Sep 17 00:00:00 2001
From: Oskari Lavinto
Date: Wed, 19 Oct 2022 15:45:55 +0000
Subject: [PATCH 17/30] Translated using Weblate (Finnish) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/fi/
---
Emby.Server.Implementations/Localization/Core/fi.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/fi.json b/Emby.Server.Implementations/Localization/Core/fi.json
index f0cafd1c0d..ec72d58dd6 100644
--- a/Emby.Server.Implementations/Localization/Core/fi.json
+++ b/Emby.Server.Implementations/Localization/Core/fi.json
@@ -122,5 +122,6 @@
"TaskOptimizeDatabase": "Optimoi tietokanta",
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
"TaskKeyframeExtractor": "Avainkuvien purkain",
- "External": "Ulkoinen"
+ "External": "Ulkoinen",
+ "HearingImpaired": "Kuulorajoitteinen"
}
From d6cf692490c9d081ce0567a918019b9ab625d216 Mon Sep 17 00:00:00 2001
From: kevin
Date: Wed, 19 Oct 2022 08:25:43 +0000
Subject: [PATCH 18/30] Translated using Weblate (Albanian) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/sq/
---
Emby.Server.Implementations/Localization/Core/sq.json | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/sq.json b/Emby.Server.Implementations/Localization/Core/sq.json
index 2766dab06a..d1b73a3eb9 100644
--- a/Emby.Server.Implementations/Localization/Core/sq.json
+++ b/Emby.Server.Implementations/Localization/Core/sq.json
@@ -119,5 +119,9 @@
"Forced": "I detyruar",
"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.",
- "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"
}
From 53ee43dc199c39d21867e84b6a428f3409c82b6e Mon Sep 17 00:00:00 2001
From: Urtzi Odriozola
Date: Wed, 19 Oct 2022 22:23:49 +0000
Subject: [PATCH 19/30] Translated using Weblate (Basque) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/eu/
---
Emby.Server.Implementations/Localization/Core/eu.json | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/eu.json b/Emby.Server.Implementations/Localization/Core/eu.json
index dfedce7b3a..d657ac7b69 100644
--- a/Emby.Server.Implementations/Localization/Core/eu.json
+++ b/Emby.Server.Implementations/Localization/Core/eu.json
@@ -116,5 +116,12 @@
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
"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"
}
From 509c6ec24ca35b2e16561808792cd581c5f9d8fc Mon Sep 17 00:00:00 2001
From: Polaris
Date: Tue, 18 Oct 2022 20:05:01 +0000
Subject: [PATCH 20/30] Translated using Weblate (Lojban) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/jbo/
---
Emby.Server.Implementations/Localization/Core/jbo.json | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/jbo.json b/Emby.Server.Implementations/Localization/Core/jbo.json
index 0967ef424b..1b47bb2f23 100644
--- a/Emby.Server.Implementations/Localization/Core/jbo.json
+++ b/Emby.Server.Implementations/Localization/Core/jbo.json
@@ -1 +1,7 @@
-{}
+{
+ "Albums": "lo albuma",
+ "Artists": "lo larpra",
+ "Books": "lo cukta",
+ "HeaderAlbumArtists": "lo albuma larpra",
+ "Playlists": "lo zgipor"
+}
From b7882db9c72e2a07d7814e7eaf038d69837b4972 Mon Sep 17 00:00:00 2001
From: Shadowghost
Date: Fri, 21 Oct 2022 10:09:45 +0200
Subject: [PATCH 21/30] Prevent host lookup on GetSmartUrl for HTTP requests
---
Emby.Server.Implementations/ApplicationHost.cs | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 909972469e..8db55a6aea 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -1088,15 +1088,7 @@ namespace Emby.Server.Implementations
return GetLocalApiUrl(request.Host.Host, request.Scheme, requestPort);
}
- // Published server ends with a /
- 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);
+ return GetSmartApiUrl(request.HttpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback);
}
///
From 7ad0c9ba24a5f248c2f5b0d89ff096779d22a2b8 Mon Sep 17 00:00:00 2001
From: MrTimscampi
Date: Thu, 7 Apr 2022 12:15:25 +0200
Subject: [PATCH 22/30] Migrate MusicBrainz plugin to MetaBrainz.MusicBrainz
Co-authored-by: crobibero
Co-authored-by: Shadowghost
---
.../MediaBrowser.Providers.csproj | 1 +
.../Configuration/PluginConfiguration.cs | 67 +-
.../MusicBrainzAlbumArtistExternalId.cs | 33 +-
.../MusicBrainz/MusicBrainzAlbumExternalId.cs | 33 +-
.../MusicBrainz/MusicBrainzAlbumProvider.cs | 873 ++++--------------
.../MusicBrainzArtistExternalId.cs | 33 +-
.../MusicBrainz/MusicBrainzArtistProvider.cs | 332 +++----
.../MusicBrainzOtherArtistExternalId.cs | 33 +-
.../MusicBrainzReleaseGroupExternalId.cs | 33 +-
.../Plugins/MusicBrainz/MusicBrainzTrackId.cs | 33 +-
.../Plugins/MusicBrainz/Plugin.cs | 70 +-
.../EnumerableExtensions.cs | 70 +-
.../Parsers/MusicAlbumNfoProviderTests.cs | 2 +-
.../Parsers/MusicArtistNfoParserTests.cs | 2 +-
14 files changed, 502 insertions(+), 1113 deletions(-)
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 3a0e9a225b..b00c036e5a 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -17,6 +17,7 @@
+
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 9c27bd7d3f..1d4b88087d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -1,37 +1,58 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Model.Plugins;
+using MetaBrainz.MusicBrainz;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
-namespace MediaBrowser.Providers.Plugins.MusicBrainz
+///
+/// MusicBrainz plugin configuration.
+///
+public class PluginConfiguration : BasePluginConfiguration
{
- public class PluginConfiguration : BasePluginConfiguration
- {
- private string _server = Plugin.DefaultServer;
+ private const string DefaultServer = "musicbrainz.org";
+
+ private const double DefaultRateLimit = 1.0;
- private long _rateLimit = Plugin.DefaultRateLimit;
+ private string _server = DefaultServer;
+
+ private double _rateLimit = DefaultRateLimit;
+
+ ///
+ /// Gets or sets the server url.
+ ///
+ public string Server
+ {
+ get => _server;
- public string Server
+ set
{
- get => _server;
- set => _server = value.TrimEnd('/');
+ _server = value.TrimEnd('/');
+ Query.DefaultServer = _server;
}
+ }
- public long RateLimit
+ ///
+ /// Gets or sets the rate limit.
+ ///
+ public double RateLimit
+ {
+ get => _rateLimit;
+ set
{
- get => _rateLimit;
- set
+ if (value < DefaultRateLimit && _server == DefaultServer)
{
- if (value < Plugin.DefaultRateLimit && _server == Plugin.DefaultServer)
- {
- _rateLimit = Plugin.DefaultRateLimit;
- }
- else
- {
- _rateLimit = value;
- }
+ _rateLimit = DefaultRateLimit;
+ }
+ else
+ {
+ _rateLimit = value;
}
- }
- public bool ReplaceArtistName { get; set; }
+ Query.DelayBetweenRequests = _rateLimit;
+ }
}
+
+ ///
+ /// Gets or sets a value indicating whether to replace the artist name.
+ ///
+ public bool ReplaceArtistName { get; set; }
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
index c54cdda3d3..f7850781e0 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz album artist external id.
+///
+public class MusicBrainzAlbumArtistExternalId : IExternalId
{
- public class MusicBrainzAlbumArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
index 8f7fadd060..a9d4472e7d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz album external id.
+///
+public class MusicBrainzAlbumExternalId : IExternalId
{
- public class MusicBrainzAlbumExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 915fb97fd2..e88a51c197 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -1,805 +1,256 @@
-#nullable disable
-
-#pragma warning disable CS1591, SA1401
-
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using System.Xml;
-using MediaBrowser.Common.Net;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-using Microsoft.Extensions.Logging;
-
-namespace MediaBrowser.Providers.Music
-{
- public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder, IDisposable
- {
- ///
- /// For each single MB lookup/search, this is the maximum number of
- /// attempts that shall be made whilst receiving a 503 Server
- /// Unavailable (indicating throttled) response.
- ///
- private const uint MusicBrainzQueryAttempts = 5u;
-
- ///
- /// The Jellyfin user-agent is unrestricted but source IP must not exceed
- /// one request per second, therefore we rate limit to avoid throttling.
- /// Be prudent, use a value slightly above the minimum required.
- /// https://musicbrainz.org/doc/XML_Web_Service/Rate_Limiting.
- ///
- private readonly long _musicBrainzQueryIntervalMs;
-
- private readonly IHttpClientFactory _httpClientFactory;
- private readonly ILogger _logger;
-
- private readonly string _musicBrainzBaseUrl;
-
- private SemaphoreSlim _apiRequestLock = new SemaphoreSlim(1, 1);
- private Stopwatch _stopWatchMusicBrainz = new Stopwatch();
-
- public MusicBrainzAlbumProvider(
- IHttpClientFactory httpClientFactory,
- ILogger logger)
- {
- _httpClientFactory = httpClientFactory;
- _logger = logger;
+using MediaBrowser.Providers.Music;
+using MetaBrainz.MusicBrainz;
+using MetaBrainz.MusicBrainz.Interfaces.Entities;
+using MetaBrainz.MusicBrainz.Interfaces.Searches;
- _musicBrainzBaseUrl = Plugin.Instance.Configuration.Server;
- _musicBrainzQueryIntervalMs = Plugin.Instance.Configuration.RateLimit;
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
- // Use a stopwatch to ensure we don't exceed the MusicBrainz rate limit
- _stopWatchMusicBrainz.Start();
+///
+/// Music album metadata provider for MusicBrainz.
+///
+public class MusicBrainzAlbumProvider : IRemoteMetadataProvider, IHasOrder, IDisposable
+{
+ private readonly Query _musicBrainzQuery;
- Current = this;
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MusicBrainzAlbumProvider()
+ {
+ _musicBrainzQuery = new Query();
+ }
- internal static MusicBrainzAlbumProvider Current { get; private set; }
+ ///
+ public string Name => "MusicBrainz";
- ///
- public string Name => "MusicBrainz";
+ ///
+ public int Order => 0;
- ///
- public int Order => 0;
+ ///
+ public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var releaseId = searchInfo.GetReleaseId();
+ var releaseGroupId = searchInfo.GetReleaseGroupId();
- ///
- public async Task> GetSearchResults(AlbumInfo searchInfo, CancellationToken cancellationToken)
+ if (!string.IsNullOrEmpty(releaseId))
{
- var releaseId = searchInfo.GetReleaseId();
- var releaseGroupId = searchInfo.GetReleaseGroupId();
-
- string url;
-
- if (!string.IsNullOrEmpty(releaseId))
- {
- url = "/ws/2/release/?query=reid:" + releaseId.ToString(CultureInfo.InvariantCulture);
- }
- else if (!string.IsNullOrEmpty(releaseGroupId))
- {
- url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
-
- if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
- {
- url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND arid:{1}",
- WebUtility.UrlEncode(searchInfo.Name),
- artistMusicBrainzId);
- }
- else
- {
- // I'm sure there is a better way but for now it resolves search for 12" Mixes
- var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
-
- url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
- WebUtility.UrlEncode(queryName),
- WebUtility.UrlEncode(searchInfo.GetAlbumArtist()));
- }
- }
-
- if (!string.IsNullOrWhiteSpace(url))
- {
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return GetResultsFromResponse(stream);
- }
-
- return Enumerable.Empty();
+ var releaseResult = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false);
+ return GetResultFromResponse(releaseResult).SingleItemAsEnumerable();
}
- private IEnumerable GetResultsFromResponse(Stream stream)
+ if (!string.IsNullOrEmpty(releaseGroupId))
{
- 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);
- var results = ReleaseResult.Parse(reader);
-
- return results.Select(i =>
- {
- var result = new RemoteSearchResult
- {
- Name = i.Title,
- ProductionYear = i.Year
- };
-
- if (i.Artists.Count > 0)
- {
- result.AlbumArtist = new RemoteSearchResult
- {
- SearchProviderName = Name,
- Name = i.Artists[0].Item1
- };
-
- result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
- }
-
- if (!string.IsNullOrWhiteSpace(i.ReleaseId))
- {
- result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
- }
-
- if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
- {
- result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
- }
-
- return result;
- });
+ var releaseGroupResult = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.ReleaseGroups, null, cancellationToken).ConfigureAwait(false);
+ return GetResultsFromResponse(releaseGroupResult.Releases);
}
- ///
- public async Task> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
- {
- var releaseId = info.GetReleaseId();
- var releaseGroupId = info.GetReleaseGroupId();
-
- var result = new MetadataResult
- {
- Item = new MusicAlbum()
- };
-
- // If we have a release group Id but not a release Id...
- if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
- {
- releaseId = await GetReleaseIdFromReleaseGroupId(releaseGroupId, cancellationToken).ConfigureAwait(false);
- result.HasMetadata = true;
- }
-
- if (string.IsNullOrWhiteSpace(releaseId))
- {
- var artistMusicBrainzId = info.GetMusicBrainzArtistId();
-
- var releaseResult = await GetReleaseResult(artistMusicBrainzId, info.GetAlbumArtist(), info.Name, cancellationToken).ConfigureAwait(false);
+ var artistMusicBrainzId = searchInfo.GetMusicBrainzArtistId();
- if (releaseResult != null)
- {
- if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseId))
- {
- releaseId = releaseResult.ReleaseId;
- result.HasMetadata = true;
- }
-
- if (!string.IsNullOrWhiteSpace(releaseResult.ReleaseGroupId))
- {
- releaseGroupId = releaseResult.ReleaseGroupId;
- result.HasMetadata = true;
- }
-
- result.Item.ProductionYear = releaseResult.Year;
- result.Item.Overview = releaseResult.Overview;
- }
- }
+ if (!string.IsNullOrWhiteSpace(artistMusicBrainzId))
+ {
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{searchInfo.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
- // If we have a release Id but not a release group Id...
- if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
+ if (releaseSearchResults.Results.Count > 0)
{
- releaseGroupId = await GetReleaseGroupFromReleaseId(releaseId, cancellationToken).ConfigureAwait(false);
- result.HasMetadata = true;
+ return GetResultsFromResponse(releaseSearchResults.Results);
}
+ }
+ else
+ {
+ // I'm sure there is a better way but for now it resolves search for 12" Mixes
+ var queryName = searchInfo.Name.Replace("\"", string.Empty, StringComparison.Ordinal);
- if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
- {
- result.HasMetadata = true;
- }
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{queryName}\" AND artist:\"{searchInfo.GetAlbumArtist()}\"c", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
- if (result.HasMetadata)
+ if (releaseSearchResults.Results.Count > 0)
{
- if (!string.IsNullOrEmpty(releaseId))
- {
- result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
- }
-
- if (!string.IsNullOrEmpty(releaseGroupId))
- {
- result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
- }
+ return GetResultsFromResponse(releaseSearchResults.Results);
}
-
- return result;
}
- private Task GetReleaseResult(string artistMusicBrainId, string artistName, string albumName, CancellationToken cancellationToken)
- {
- if (!string.IsNullOrEmpty(artistMusicBrainId))
- {
- return GetReleaseResult(albumName, artistMusicBrainId, cancellationToken);
- }
-
- if (string.IsNullOrWhiteSpace(artistName))
- {
- return Task.FromResult(new ReleaseResult());
- }
+ return Enumerable.Empty();
+ }
- return GetReleaseResultByArtistName(albumName, artistName, cancellationToken);
+ private IEnumerable GetResultsFromResponse(IEnumerable>? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
+ {
+ yield break;
}
- private async Task GetReleaseResult(string albumName, string artistId, CancellationToken cancellationToken)
+ foreach (var result in releaseSearchResults)
{
- var url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND arid:{1}",
- WebUtility.UrlEncode(albumName),
- artistId);
-
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- 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);
- return ReleaseResult.Parse(reader).FirstOrDefault();
+ yield return GetResultFromResponse(result.Item);
}
+ }
- private async Task GetReleaseResultByArtistName(string albumName, string artistName, CancellationToken cancellationToken)
+ private IEnumerable GetResultsFromResponse(IEnumerable? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
{
- var url = string.Format(
- CultureInfo.InvariantCulture,
- "/ws/2/release/?query=\"{0}\" AND artist:\"{1}\"",
- WebUtility.UrlEncode(albumName),
- WebUtility.UrlEncode(artistName));
-
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- 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);
- return ReleaseResult.Parse(reader).FirstOrDefault();
+ yield break;
}
- private static (string Name, string ArtistId) ParseArtistCredit(XmlReader reader)
+ foreach (var result in releaseSearchResults)
{
- 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-credit":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseArtistNameCredit(subReader);
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return default;
+ yield return GetResultFromResponse(result);
}
+ }
- private static (string Name, string ArtistId) ParseArtistNameCredit(XmlReader reader)
+ private RemoteSearchResult GetResultFromResponse(IRelease releaseSearchResult)
+ {
+ var searchResult = new RemoteSearchResult
{
- reader.MoveToContent();
- reader.Read();
+ Name = releaseSearchResult.Title,
+ ProductionYear = releaseSearchResult.Date?.Year,
+ PremiereDate = releaseSearchResult.Date?.NearestDate
+ };
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ if (releaseSearchResult.ArtistCredit?.Count > 0)
+ {
+ searchResult.AlbumArtist = new RemoteSearchResult
+ {
+ SearchProviderName = Name,
+ Name = releaseSearchResult.ArtistCredit[0].Name
+ };
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ if (releaseSearchResult.ArtistCredit[0].Artist?.Id is not null)
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "artist":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- var id = reader.GetAttribute("id");
- using var subReader = reader.ReadSubtree();
- return ParseArtistArtistCredit(subReader, id);
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
+ searchResult.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, releaseSearchResult.ArtistCredit[0].Artist!.Id.ToString());
}
-
- return (null, null);
}
- private static (string Name, string ArtistId) ParseArtistArtistCredit(XmlReader reader, string artistId)
- {
- reader.MoveToContent();
- reader.Read();
-
- string name = null;
-
- // 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":
- {
- name = reader.ReadElementContentAsString();
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
- return (name, artistId);
+ if (releaseSearchResult.ReleaseGroup?.Id is not null)
+ {
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseSearchResult.ReleaseGroup.Id.ToString());
}
- private async Task GetReleaseIdFromReleaseGroupId(string releaseGroupId, CancellationToken cancellationToken)
- {
- var url = "/ws/2/release?release-group=" + releaseGroupId.ToString(CultureInfo.InvariantCulture);
+ return searchResult;
+ }
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings
- {
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true
- };
+ ///
+ public async Task> GetMetadata(AlbumInfo info, CancellationToken cancellationToken)
+ {
+ // TODO: This sets essentially nothing. As-is, it's mostly useless. Make it actually pull metadata and use it.
+ var releaseId = info.GetReleaseId();
+ var releaseGroupId = info.GetReleaseGroupId();
- using var reader = XmlReader.Create(oReader, settings);
- var result = ReleaseResult.Parse(reader).FirstOrDefault();
+ var result = new MetadataResult
+ {
+ Item = new MusicAlbum()
+ };
- return result?.ReleaseId;
+ // If there is a release group, but no release ID, try to match the release
+ if (string.IsNullOrWhiteSpace(releaseId) && !string.IsNullOrWhiteSpace(releaseGroupId))
+ {
+ // TODO: Actually try to match the release. Simply taking the first result is stupid.
+ var releaseGroup = await _musicBrainzQuery.LookupReleaseGroupAsync(new Guid(releaseGroupId), Include.ReleaseGroups, null, cancellationToken).ConfigureAwait(false);
+ var release = releaseGroup.Releases?.Count > 0 ? releaseGroup.Releases[0] : null;
+ releaseId = release?.Id.ToString();
+ result.HasMetadata = true;
}
- ///
- /// Gets the release group id internal.
- ///
- /// The release entry id.
- /// The cancellation token.
- /// Task{System.String}.
- private async Task GetReleaseGroupFromReleaseId(string releaseEntryId, CancellationToken cancellationToken)
+ // If there is no release ID, lookup a release with the info we have
+ if (string.IsNullOrWhiteSpace(releaseId))
{
- var url = "/ws/2/release-group/?query=reid:" + releaseEntryId.ToString(CultureInfo.InvariantCulture);
+ var artistMusicBrainzId = info.GetMusicBrainzArtistId();
+ IRelease? releaseResult = null;
- using var response = await GetMusicBrainzResponse(url, cancellationToken).ConfigureAwait(false);
- await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- using var oReader = new StreamReader(stream, Encoding.UTF8);
- var settings = new XmlReaderSettings
+ if (!string.IsNullOrEmpty(artistMusicBrainzId))
{
- ValidationType = ValidationType.None,
- CheckCharacters = false,
- IgnoreProcessingInstructions = true,
- IgnoreComments = true,
- Async = true
- };
-
- using var reader = XmlReader.Create(oReader, settings);
- await reader.MoveToContentAsync().ConfigureAwait(false);
- await reader.ReadAsync().ConfigureAwait(false);
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-group-list":
- {
- if (reader.IsEmptyElement)
- {
- await reader.ReadAsync().ConfigureAwait(false);
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return GetFirstReleaseGroupId(subReader);
- }
-
- default:
- {
- await reader.SkipAsync().ConfigureAwait(false);
- break;
- }
- }
- }
- else
- {
- await reader.ReadAsync().ConfigureAwait(false);
- }
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND arid:{artistMusicBrainzId}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
}
-
- return null;
- }
-
- private string GetFirstReleaseGroupId(XmlReader reader)
- {
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
+ else if (!string.IsNullOrEmpty(info.GetAlbumArtist()))
{
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-group":
- {
- return reader.GetAttribute("id");
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
+ var releaseSearchResults = await _musicBrainzQuery.FindReleasesAsync($"\"{info.Name}\" AND artist:{info.GetAlbumArtist()}", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ releaseResult = releaseSearchResults.Results.Count > 0 ? releaseSearchResults.Results[0].Item : null;
}
- return null;
- }
-
- ///
- /// Makes request to MusicBrainz server and awaits a response.
- /// A 503 Service Unavailable response indicates throttling to maintain a rate limit.
- /// A number of retries shall be made in order to try and satisfy the request before
- /// giving up and returning null.
- ///
- /// Address of MusicBrainz server.
- /// CancellationToken to use for method.
- /// Returns response from MusicBrainz service.
- internal async Task GetMusicBrainzResponse(string url, CancellationToken cancellationToken)
- {
- await _apiRequestLock.WaitAsync(cancellationToken).ConfigureAwait(false);
-
- try
+ if (releaseResult != null)
{
- HttpResponseMessage response;
- var attempts = 0u;
- var requestUrl = _musicBrainzBaseUrl.TrimEnd('/') + url;
+ releaseId = releaseResult.Id.ToString();
- do
+ if (releaseResult.ReleaseGroup?.Id is not null)
{
- attempts++;
-
- if (_stopWatchMusicBrainz.ElapsedMilliseconds < _musicBrainzQueryIntervalMs)
- {
- // MusicBrainz is extremely adamant about limiting to one request per second.
- var delayMs = _musicBrainzQueryIntervalMs - _stopWatchMusicBrainz.ElapsedMilliseconds;
- await Task.Delay((int)delayMs, cancellationToken).ConfigureAwait(false);
- }
-
- // Write time since last request to debug log as evidence we're meeting rate limit
- // requirement, before resetting stopwatch back to zero.
- _logger.LogDebug("GetMusicBrainzResponse: Time since previous request: {0} ms", _stopWatchMusicBrainz.ElapsedMilliseconds);
- _stopWatchMusicBrainz.Restart();
-
- using var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
- response = await _httpClientFactory
- .CreateClient(NamedClient.MusicBrainz)
- .SendAsync(request, cancellationToken)
- .ConfigureAwait(false);
-
- // We retry a finite number of times, and only whilst MB is indicating 503 (throttling).
+ releaseGroupId = releaseResult.ReleaseGroup.Id.ToString();
}
- while (attempts < MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable);
- // Log error if unable to query MB database due to throttling.
- if (attempts == MusicBrainzQueryAttempts && response.StatusCode == HttpStatusCode.ServiceUnavailable)
- {
- _logger.LogError("GetMusicBrainzResponse: 503 Service Unavailable (throttled) response received {0} times whilst requesting {1}", attempts, requestUrl);
- }
-
- return response;
- }
- finally
- {
- _apiRequestLock.Release();
+ result.HasMetadata = true;
+ result.Item.ProductionYear = releaseResult.Date?.Year;
+ result.Item.Overview = releaseResult.Annotation;
}
}
- ///
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ // If we have a release ID but not a release group ID, lookup the release group
+ if (!string.IsNullOrWhiteSpace(releaseId) && string.IsNullOrWhiteSpace(releaseGroupId))
{
- throw new NotImplementedException();
+ var release = await _musicBrainzQuery.LookupReleaseAsync(new Guid(releaseId), Include.Releases, cancellationToken).ConfigureAwait(false);
+ releaseGroupId = release.ReleaseGroup?.Id.ToString();
+ result.HasMetadata = true;
}
- protected virtual void Dispose(bool disposing)
+ // If we have a release ID and a release group ID
+ if (!string.IsNullOrWhiteSpace(releaseId) || !string.IsNullOrWhiteSpace(releaseGroupId))
{
- if (disposing)
- {
- _apiRequestLock?.Dispose();
- }
+ result.HasMetadata = true;
}
- ///
- public void Dispose()
+ if (result.HasMetadata)
{
- Dispose(true);
- GC.SuppressFinalize(this);
- }
-
- private class ReleaseResult
- {
- public string ReleaseId;
- public string ReleaseGroupId;
- public string Title;
- public string Overview;
- public int? Year;
-
- public List<(string, string)> Artists = new();
-
- public static IEnumerable Parse(XmlReader reader)
+ if (!string.IsNullOrEmpty(releaseId))
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseReleaseList(subReader).ToList();
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return Enumerable.Empty();
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseId);
}
- private static IEnumerable ParseReleaseList(XmlReader reader)
+ if (!string.IsNullOrEmpty(releaseGroupId))
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "release":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- var releaseId = reader.GetAttribute("id");
-
- using var subReader = reader.ReadSubtree();
- var release = ParseRelease(subReader, releaseId);
- if (release != null)
- {
- yield return release;
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, releaseGroupId);
}
+ }
- private static ReleaseResult ParseRelease(XmlReader reader, string releaseId)
- {
- var result = new ReleaseResult
- {
- ReleaseId = releaseId
- };
-
- reader.MoveToContent();
- reader.Read();
+ return result;
+ }
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ ///
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "title":
- {
- result.Title = reader.ReadElementContentAsString();
- break;
- }
-
- case "date":
- {
- var val = reader.ReadElementContentAsString();
- if (DateTime.TryParse(val, out var date))
- {
- result.Year = date.Year;
- }
-
- break;
- }
-
- case "annotation":
- {
- result.Overview = reader.ReadElementContentAsString();
- break;
- }
-
- case "release-group":
- {
- result.ReleaseGroupId = reader.GetAttribute("id");
- reader.Skip();
- break;
- }
-
- case "artist-credit":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- break;
- }
-
- using var subReader = reader.ReadSubtree();
- var artist = ParseArtistCredit(subReader);
-
- if (!string.IsNullOrEmpty(artist.Name))
- {
- result.Artists.Add(artist);
- }
-
- break;
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
- return result;
- }
+ ///
+ /// Dispose all resources.
+ ///
+ /// Whether to dispose.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _musicBrainzQuery.Dispose();
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
index 941ffea721..e2fb5621bc 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrains Artist ExternalId.
+///
+public class MusicBrainzArtistExternalId : IExternalId
{
- public class MusicBrainzArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is MusicArtist;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is MusicArtist;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 906a42f36d..1d2c9c2c81 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -1,15 +1,7 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
using System.Linq;
-using System.Net;
using System.Net.Http;
-using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
@@ -18,257 +10,159 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-
-namespace MediaBrowser.Providers.Music
-{
- public class MusicBrainzArtistProvider : IRemoteMetadataProvider
- {
- public string Name => "MusicBrainz";
-
- ///
- public async Task> 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);
-
- 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('/', ' ');
+using MediaBrowser.Providers.Music;
+using MetaBrainz.MusicBrainz;
+using MetaBrainz.MusicBrainz.Interfaces.Entities;
+using MetaBrainz.MusicBrainz.Interfaces.Searches;
- var url = string.Format(CultureInfo.InvariantCulture, "/ws/2/artist/?query=\"{0}\"&dismax=true", UrlEncode(nameToSearch));
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
- 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();
+///
+/// MusicBrainz artist provider.
+///
+public class MusicBrainzArtistProvider : IRemoteMetadataProvider, IDisposable
+{
+ private readonly Query _musicBrainzQuery;
- if (results.Count > 0)
- {
- return results;
- }
- }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public MusicBrainzArtistProvider()
+ {
+ _musicBrainzQuery = new Query();
+ }
- 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));
+ ///
+ public string Name => "MusicBrainz";
- 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);
- }
- }
+ ///
+ public async Task> GetSearchResults(ArtistInfo searchInfo, CancellationToken cancellationToken)
+ {
+ var artistId = searchInfo.GetMusicBrainzArtistId();
- return Enumerable.Empty();
+ if (!string.IsNullOrWhiteSpace(artistId))
+ {
+ var artistResult = await _musicBrainzQuery.LookupArtistAsync(new Guid(artistId), Include.Artists, null, null, cancellationToken).ConfigureAwait(false);
+ return GetResultFromResponse(artistResult).SingleItemAsEnumerable();
}
- private IEnumerable GetResultsFromResponse(Stream stream)
+ var artistSearchResults = await _musicBrainzQuery.FindArtistsAsync($"\"{searchInfo.Name}\"", null, null, false, cancellationToken)
+ .ConfigureAwait(false);
+ if (artistSearchResults.Results.Count > 0)
{
- 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);
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- while (!reader.EOF && reader.ReadState == ReadState.Interactive)
- {
- if (reader.NodeType == XmlNodeType.Element)
- {
- switch (reader.Name)
- {
- case "artist-list":
- {
- if (reader.IsEmptyElement)
- {
- reader.Read();
- continue;
- }
-
- using var subReader = reader.ReadSubtree();
- return ParseArtistList(subReader).ToList();
- }
-
- default:
- {
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
-
- return Enumerable.Empty();
+ return GetResultsFromResponse(artistSearchResults.Results);
}
- private IEnumerable ParseArtistList(XmlReader reader)
+ if (searchInfo.Name.HasDiacritics())
{
- reader.MoveToContent();
- reader.Read();
-
- // Loop through each element
- 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)
- {
- switch (reader.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();
- }
+ return GetResultsFromResponse(artistAccentsSearchResults.Results);
}
}
- private RemoteSearchResult ParseArtist(XmlReader reader, string artistId)
+ return Enumerable.Empty();
+ }
+
+ private IEnumerable GetResultsFromResponse(IEnumerable>? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
{
- var result = new RemoteSearchResult();
+ yield break;
+ }
- reader.MoveToContent();
- reader.Read();
+ foreach (var result in releaseSearchResults)
+ {
+ yield return GetResultFromResponse(result.Item);
+ }
+ }
- // http://stackoverflow.com/questions/2299632/why-does-xmlreader-skip-every-other-element-if-there-is-no-whitespace-separator
+ private IEnumerable GetResultsFromResponse(IEnumerable? releaseSearchResults)
+ {
+ if (releaseSearchResults is null)
+ {
+ yield break;
+ }
- // 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;
- }
+ foreach (var result in releaseSearchResults)
+ {
+ yield return GetResultFromResponse(result);
+ }
+ }
- case "annotation":
- {
- result.Overview = reader.ReadElementContentAsString();
- break;
- }
+ private RemoteSearchResult GetResultFromResponse(IArtist artist)
+ {
+ var searchResult = new RemoteSearchResult
+ {
+ Name = artist.Name,
+ ProductionYear = artist.LifeSpan?.Begin?.Year,
+ PremiereDate = artist.LifeSpan?.Begin?.NearestDate
+ };
- default:
- {
- // there is sort-name if ever needed
- reader.Skip();
- break;
- }
- }
- }
- else
- {
- reader.Read();
- }
- }
+ searchResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Id.ToString());
- result.SetProviderId(MetadataProvider.MusicBrainzArtist, artistId);
+ return searchResult;
+ }
- if (string.IsNullOrWhiteSpace(artistId) || string.IsNullOrWhiteSpace(result.Name))
- {
- return null;
- }
+ ///
+ public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
+ {
+ var result = new MetadataResult { Item = new MusicArtist() };
- return result;
- }
+ var musicBrainzId = info.GetMusicBrainzArtistId();
- ///
- public async Task> GetMetadata(ArtistInfo info, CancellationToken cancellationToken)
+ if (string.IsNullOrWhiteSpace(musicBrainzId))
{
- var result = new MetadataResult
- {
- Item = new MusicArtist()
- };
+ var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
- var musicBrainzId = info.GetMusicBrainzArtistId();
+ var singleResult = searchResults.FirstOrDefault();
- if (string.IsNullOrWhiteSpace(musicBrainzId))
+ if (singleResult != null)
{
- var searchResults = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
+ musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
+ result.Item.Overview = singleResult.Overview;
- var singleResult = searchResults.FirstOrDefault();
-
- if (singleResult != null)
+ if (Plugin.Instance!.Configuration.ReplaceArtistName)
{
- musicBrainzId = singleResult.GetProviderId(MetadataProvider.MusicBrainzArtist);
- result.Item.Overview = singleResult.Overview;
-
- if (Plugin.Instance.Configuration.ReplaceArtistName)
- {
- result.Item.Name = singleResult.Name;
- }
+ result.Item.Name = singleResult.Name;
}
}
-
- if (!string.IsNullOrWhiteSpace(musicBrainzId))
- {
- result.HasMetadata = true;
- result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
- }
-
- return result;
}
- ///
- /// Encodes an URL.
- ///
- /// The name.
- /// System.String.
- private static string UrlEncode(string name)
+ if (!string.IsNullOrWhiteSpace(musicBrainzId))
{
- return WebUtility.UrlEncode(name);
+ result.HasMetadata = true;
+ result.Item.SetProviderId(MetadataProvider.MusicBrainzArtist, musicBrainzId);
}
- public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ return result;
+ }
+
+ ///
+ public Task GetImageResponse(string url, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Dispose all resources.
+ ///
+ /// Whether to dispose.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
{
- throw new NotImplementedException();
+ _musicBrainzQuery.Dispose();
}
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
index 05db2d98f7..fdaa5574f0 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzOtherArtistExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz other artist external id.
+///
+public class MusicBrainzOtherArtistExternalId : IExternalId
{
- public class MusicBrainzOtherArtistExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzArtist.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzArtist.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/artist/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
index acb652fe01..0baab9955d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzReleaseGroupExternalId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz release group external id.
+///
+public class MusicBrainzReleaseGroupExternalId : IExternalId
{
- public class MusicBrainzReleaseGroupExternalId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/release-group/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is Audio || item is MusicAlbum;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio or MusicAlbum;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
index 14805b9b79..5c974c4111 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzTrackId.cs
@@ -1,28 +1,27 @@
-#pragma warning disable CS1591
-
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Music
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
+
+///
+/// MusicBrainz track id.
+///
+public class MusicBrainzTrackId : IExternalId
{
- public class MusicBrainzTrackId : IExternalId
- {
- ///
- public string ProviderName => "MusicBrainz";
+ ///
+ public string ProviderName => "MusicBrainz";
- ///
- public string Key => MetadataProvider.MusicBrainzTrack.ToString();
+ ///
+ public string Key => MetadataProvider.MusicBrainzTrack.ToString();
- ///
- public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
+ ///
+ public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
- ///
- public string? UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
+ ///
+ public string? UrlFormatString => Plugin.Instance!.Configuration.Server + "/track/{0}";
- ///
- public bool Supports(IHasProviderIds item) => item is Audio;
- }
+ ///
+ public bool Supports(IHasProviderIds item) => item is Audio;
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index cfa10dd648..270d76e6db 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,45 +1,63 @@
-#nullable disable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
+using System.Net.Http.Headers;
+using System.Reflection;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
+using MetaBrainz.MusicBrainz;
+
+namespace MediaBrowser.Providers.Plugins.MusicBrainz;
-namespace MediaBrowser.Providers.Plugins.MusicBrainz
+///
+/// Plugin instance.
+///
+public class Plugin : BasePlugin, IHasWebPages
{
- public class Plugin : BasePlugin, IHasWebPages
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
{
- public const string DefaultServer = "https://musicbrainz.org";
-
- public const long DefaultRateLimit = 2000u;
+ Instance = this;
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
- : base(applicationPaths, xmlSerializer)
- {
- Instance = this;
- }
+ // TODO: Change this to "JellyfinMusicBrainzPlugin" once we take it out of the server repo.
+ Query.DefaultUserAgent.Add(new ProductInfoHeaderValue("Jellyfin", Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)));
+ Query.DefaultUserAgent.Add(new ProductInfoHeaderValue("(apps@jellyfin.org)"));
+ Query.DelayBetweenRequests = Instance.Configuration.RateLimit;
+ Query.DefaultServer = Instance.Configuration.Server;
+ }
- public static Plugin Instance { get; private set; }
+ ///
+ /// Gets the current plugin instance.
+ ///
+ public static Plugin? Instance { get; private set; }
- public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
+ ///
+ public override Guid Id => new Guid("8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a");
- public override string Name => "MusicBrainz";
+ ///
+ public override string Name => "MusicBrainz";
- public override string Description => "Get artist and album metadata from any MusicBrainz server.";
+ ///
+ 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";
+ ///
+ // TODO remove when plugin removed from server.
+ public override string ConfigurationFileName => "Jellyfin.Plugin.MusicBrainz.xml";
- public IEnumerable GetPages()
+ ///
+ public IEnumerable GetPages()
+ {
+ yield return new PluginPageInfo
{
- yield return new PluginPageInfo
- {
- Name = Name,
- EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
- };
- }
+ Name = Name,
+ EmbeddedResourcePath = GetType().Namespace + ".Configuration.config.html"
+ };
}
}
diff --git a/src/Jellyfin.Extensions/EnumerableExtensions.cs b/src/Jellyfin.Extensions/EnumerableExtensions.cs
index a31a57dc65..fd46358a4f 100644
--- a/src/Jellyfin.Extensions/EnumerableExtensions.cs
+++ b/src/Jellyfin.Extensions/EnumerableExtensions.cs
@@ -1,42 +1,31 @@
using System;
using System.Collections.Generic;
-namespace Jellyfin.Extensions
+namespace Jellyfin.Extensions;
+
+///
+/// Static extensions for the interface.
+///
+public static class EnumerableExtensions
{
///
- /// Static extensions for the interface.
+ /// Determines whether the value is contained in the source collection.
///
- public static class EnumerableExtensions
+ /// An instance of the interface.
+ /// The value to look for in the collection.
+ /// The string comparison.
+ /// A value indicating whether the value is contained in the collection.
+ /// The source is null.
+ public static bool Contains(this IEnumerable source, ReadOnlySpan value, StringComparison stringComparison)
{
- ///
- /// Determines whether the value is contained in the source collection.
- ///
- /// An instance of the interface.
- /// The value to look for in the collection.
- /// The string comparison.
- /// A value indicating whether the value is contained in the collection.
- /// The source is null.
- public static bool Contains(this IEnumerable source, ReadOnlySpan value, StringComparison stringComparison)
- {
- ArgumentNullException.ThrowIfNull(source);
-
- if (source is IList list)
- {
- int len = list.Count;
- for (int i = 0; i < len; i++)
- {
- if (value.Equals(list[i], stringComparison))
- {
- return true;
- }
- }
-
- return false;
- }
+ ArgumentNullException.ThrowIfNull(source);
- foreach (string element in source)
+ if (source is IList list)
+ {
+ int len = list.Count;
+ for (int i = 0; i < len; i++)
{
- if (value.Equals(element, stringComparison))
+ if (value.Equals(list[i], stringComparison))
{
return true;
}
@@ -44,5 +33,26 @@ namespace Jellyfin.Extensions
return false;
}
+
+ foreach (string element in source)
+ {
+ if (value.Equals(element, stringComparison))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Gets an IEnumerable from a single item.
+ ///
+ /// The item to return.
+ /// The type of item.
+ /// The IEnumerable{T}.
+ public static IEnumerable SingleItemAsEnumerable(this T item)
+ {
+ yield return item;
}
}
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
index eea8cb50a7..8f276d03fe 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicAlbumNfoProviderTests.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
diff --git a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
index 8ca3dd96e3..78183d9ffd 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MusicArtistNfoParserTests.cs
@@ -7,7 +7,7 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Music;
+using MediaBrowser.Providers.Plugins.MusicBrainz;
using MediaBrowser.XbmcMetadata.Parsers;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
From 385f1cc1b8304add4df6e7132d1a9cf54e8dddd4 Mon Sep 17 00:00:00 2001
From: Shadowghost
Date: Wed, 27 Apr 2022 13:44:49 +0200
Subject: [PATCH 23/30] Apply review suggestions
---
.../Configuration/PluginConfiguration.cs | 8 +---
.../MusicBrainz/MusicBrainzAlbumProvider.cs | 37 ++++++++++++-------
.../MusicBrainzArtistExternalId.cs | 2 +-
.../MusicBrainz/MusicBrainzArtistProvider.cs | 21 ++++-------
.../Plugins/MusicBrainz/Plugin.cs | 9 +++--
5 files changed, 37 insertions(+), 40 deletions(-)
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
index 1d4b88087d..22229e377d 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/PluginConfiguration.cs
@@ -23,11 +23,7 @@ public class PluginConfiguration : BasePluginConfiguration
{
get => _server;
- set
- {
- _server = value.TrimEnd('/');
- Query.DefaultServer = _server;
- }
+ set => _server = value.TrimEnd('/');
}
///
@@ -46,8 +42,6 @@ public class PluginConfiguration : BasePluginConfiguration
{
_rateLimit = value;
}
-
- Query.DelayBetweenRequests = _rateLimit;
}
}
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index e88a51c197..4d9feca6d1 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -28,6 +28,12 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider
public MusicBrainzAlbumProvider()
{
+ MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
+ {
+ Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server;
+ Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
+ };
+
_musicBrainzQuery = new Query();
}
@@ -45,14 +51,14 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider 0)
{
- return GetResultsFromResponse(releaseSearchResults.Results);
+ return GetReleaseSearchResult(releaseSearchResults.Results);
}
}
else
@@ -77,14 +83,14 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider 0)
{
- return GetResultsFromResponse(releaseSearchResults.Results);
+ return GetReleaseSearchResult(releaseSearchResults.Results);
}
}
return Enumerable.Empty();
}
- private IEnumerable GetResultsFromResponse(IEnumerable>? releaseSearchResults)
+ private IEnumerable GetReleaseSearchResult(IEnumerable>? releaseSearchResults)
{
if (releaseSearchResults is null)
{
@@ -93,11 +99,11 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider GetResultsFromResponse(IEnumerable? releaseSearchResults)
+ private IEnumerable GetReleaseGroupResult(IEnumerable? releaseSearchResults)
{
if (releaseSearchResults is null)
{
@@ -106,11 +112,11 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider 0 ? releaseGroup.Releases[0] : null;
- releaseId = release?.Id.ToString();
- result.HasMetadata = true;
+ if (release != null)
+ {
+ releaseId = release.Id.ToString();
+ result.HasMetadata = true;
+ }
}
// If there is no release ID, lookup a release with the info we have
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
index e2fb5621bc..b89e67270a 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistExternalId.cs
@@ -6,7 +6,7 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
///
-/// MusicBrains Artist ExternalId.
+/// MusicBrainz artist external id.
///
public class MusicBrainzArtistExternalId : IExternalId
{
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
index 1d2c9c2c81..2cc3a13bef 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzArtistProvider.cs
@@ -29,6 +29,12 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider
public MusicBrainzArtistProvider()
{
+ MusicBrainz.Plugin.Instance!.ConfigurationChanged += (_, _) =>
+ {
+ Query.DefaultServer = MusicBrainz.Plugin.Instance.Configuration.Server;
+ Query.DelayBetweenRequests = MusicBrainz.Plugin.Instance.Configuration.RateLimit;
+ };
+
_musicBrainzQuery = new Query();
}
@@ -42,7 +48,7 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider GetResultsFromResponse(IEnumerable? releaseSearchResults)
- {
- if (releaseSearchResults is null)
- {
- yield break;
- }
-
- foreach (var result in releaseSearchResults)
- {
- yield return GetResultFromResponse(result);
- }
- }
-
private RemoteSearchResult GetResultFromResponse(IArtist artist)
{
var searchResult = new RemoteSearchResult
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
index 270d76e6db..39cfd727f3 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Plugin.cs
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Net.Http.Headers;
-using System.Reflection;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
@@ -21,14 +21,15 @@ public class Plugin : BasePlugin, IHasWebPages
///
/// Instance of the interface.
/// Instance of the interface.
- public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ /// Instance of the interface.
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer, IApplicationHost applicationHost)
: base(applicationPaths, xmlSerializer)
{
Instance = this;
// TODO: Change this to "JellyfinMusicBrainzPlugin" once we take it out of the server repo.
- Query.DefaultUserAgent.Add(new ProductInfoHeaderValue("Jellyfin", Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)));
- Query.DefaultUserAgent.Add(new ProductInfoHeaderValue("(apps@jellyfin.org)"));
+ 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;
}
From 2789f8d04e859a531827ed7f63cb087890f5c773 Mon Sep 17 00:00:00 2001
From: DJSweder
Date: Fri, 21 Oct 2022 20:17:13 +0000
Subject: [PATCH 24/30] Translated using Weblate (Czech) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/cs/
---
Emby.Server.Implementations/Localization/Core/cs.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/cs.json b/Emby.Server.Implementations/Localization/Core/cs.json
index 943fc651f7..08db5a30e0 100644
--- a/Emby.Server.Implementations/Localization/Core/cs.json
+++ b/Emby.Server.Implementations/Localization/Core/cs.json
@@ -123,5 +123,6 @@
"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.",
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
- "External": "Externí"
+ "External": "Externí",
+ "HearingImpaired": "Sluchově postižení"
}
From 092c87a281f3fad9b0ae9d08c96c43686fcb855b Mon Sep 17 00:00:00 2001
From: Andi Chandler
Date: Sat, 22 Oct 2022 22:34:26 +0000
Subject: [PATCH 25/30] Translated using Weblate (English (United Kingdom))
Translation: Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/en_GB/
---
Emby.Server.Implementations/Localization/Core/en-GB.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json
index 862410c54b..2436883883 100644
--- a/Emby.Server.Implementations/Localization/Core/en-GB.json
+++ b/Emby.Server.Implementations/Localization/Core/en-GB.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "Optimise database",
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
"TaskKeyframeExtractor": "Keyframe Extractor",
- "External": "External"
+ "External": "External",
+ "HearingImpaired": "Hearing Impaired"
}
From 96e8583b2cd2996945cc519ebbb18316b7b9178d Mon Sep 17 00:00:00 2001
From: nlahmi
Date: Sat, 22 Oct 2022 22:10:45 +0000
Subject: [PATCH 26/30] Translated using Weblate (Hebrew) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/he/
---
Emby.Server.Implementations/Localization/Core/he.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index c635dab23a..694a3d688c 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabaseDescription": "דוחס את מסד הנתונים ומוריד את שטח האחסון שבשימוש. הרצה של פעולה זו לאחר סריקת הספרייה או שינויים אחרים שמשפיעים על מסד הנתונים יכולה לשפר ביצועים.",
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
- "External": "חיצוני"
+ "External": "חיצוני",
+ "HearingImpaired": "לקוי שמיעה"
}
From 4fbead582a3016ab41412a6a4aaef09e22b2e3ef Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?xos=C3=A9=20m?=
Date: Sat, 22 Oct 2022 04:56:08 +0000
Subject: [PATCH 27/30] Translated using Weblate (Galician) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/gl/
---
Emby.Server.Implementations/Localization/Core/gl.json | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/Emby.Server.Implementations/Localization/Core/gl.json b/Emby.Server.Implementations/Localization/Core/gl.json
index b433c6f68c..76a98aa54b 100644
--- a/Emby.Server.Implementations/Localization/Core/gl.json
+++ b/Emby.Server.Implementations/Localization/Core/gl.json
@@ -47,7 +47,7 @@
"HeaderFavoriteEpisodes": "Episodios Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos",
"HeaderFavoriteAlbums": "Álbunes Favoritos",
- "HeaderContinueWatching": "Seguir mirando",
+ "HeaderContinueWatching": "Seguir vendo",
"HeaderAlbumArtists": "Artistas do Album",
"Genres": "Xéneros",
"Forced": "Forzado",
@@ -119,5 +119,9 @@
"UserOnlineFromDevice": "{0} está en liña 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.",
- "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"
}
From 9b88af1fb4f69d5f5d0feb628eb2bfef60f8bad1 Mon Sep 17 00:00:00 2001
From: Franco Castillo
Date: Mon, 24 Oct 2022 04:08:43 +0000
Subject: [PATCH 28/30] Translated using Weblate (Spanish (Argentina))
Translation: Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/es_AR/
---
Emby.Server.Implementations/Localization/Core/es-AR.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index 1289172bac..8ad9e8c716 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -123,5 +123,6 @@
"TaskOptimizeDatabase": "Optimización de base de datos",
"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.",
- "TaskKeyframeExtractor": "Extractor de Fotogramas Clave"
+ "TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
+ "HearingImpaired": "Personas con discapacidad auditiva"
}
From bc4c34386bdd010503fd18008e2418bcf8ba1760 Mon Sep 17 00:00:00 2001
From: Raditya Harya
Date: Mon, 24 Oct 2022 14:47:11 +0000
Subject: [PATCH 29/30] Translated using Weblate (Indonesian) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/id/
---
Emby.Server.Implementations/Localization/Core/id.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/id.json b/Emby.Server.Implementations/Localization/Core/id.json
index 3e05525c87..695c0f4048 100644
--- a/Emby.Server.Implementations/Localization/Core/id.json
+++ b/Emby.Server.Implementations/Localization/Core/id.json
@@ -122,5 +122,6 @@
"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.",
"TaskKeyframeExtractor": "Ekstraktor Bingkai Utama",
- "External": "Luar"
+ "External": "Luar",
+ "HearingImpaired": "Gangguan Pendengaran"
}
From 790f67aac11e5c32bad19126d4e35b2afa259006 Mon Sep 17 00:00:00 2001
From: lyaschuchenko
Date: Mon, 24 Oct 2022 06:34:54 +0000
Subject: [PATCH 30/30] Translated using Weblate (Ukrainian) Translation:
Jellyfin/Jellyfin Translate-URL:
https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/uk/
---
Emby.Server.Implementations/Localization/Core/uk.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json
index 3e0fd11c8e..92ce616f2e 100644
--- a/Emby.Server.Implementations/Localization/Core/uk.json
+++ b/Emby.Server.Implementations/Localization/Core/uk.json
@@ -122,5 +122,6 @@
"TaskOptimizeDatabaseDescription": "Стискає базу даних та збільшує вільний простір. Виконання цього завдання після сканування медіатеки або внесення інших змін, які передбачають модифікацію бази даних може покращити продуктивність.",
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
- "External": "Зовнішній"
+ "External": "Зовнішній",
+ "HearingImpaired": "З порушеннями слуху"
}