mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-01-18 09:08:03 +00:00
Compare commits
38 Commits
openapi-ca
...
v10.11.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4187c6f620 | ||
|
|
e7dbb3afec | ||
|
|
f994dd6211 | ||
|
|
da254ee968 | ||
|
|
4ad3141875 | ||
|
|
b5f0199a25 | ||
|
|
6bf88c049e | ||
|
|
40a33da2a5 | ||
|
|
3596fc0693 | ||
|
|
93824dad97 | ||
|
|
e5656af1f2 | ||
|
|
c127c10458 | ||
|
|
7d1824ea27 | ||
|
|
2966d27c97 | ||
|
|
618ec4543e | ||
|
|
0e4031ae52 | ||
|
|
442af96ed9 | ||
|
|
a305204cfa | ||
|
|
75f472e6a7 | ||
|
|
cc32e8f7cb | ||
|
|
14b3085ff1 | ||
|
|
5691eee4f1 | ||
|
|
1520a697ad | ||
|
|
81b8b0ca4a | ||
|
|
ac3fa3c376 | ||
|
|
7a1c1cd342 | ||
|
|
70c32a26fa | ||
|
|
2b94bb54aa | ||
|
|
0a6e8146be | ||
|
|
305b0fdca3 | ||
|
|
d738386fe2 | ||
|
|
ca830d5be7 | ||
|
|
a5bc4524d8 | ||
|
|
175ee12bbc | ||
|
|
a725220c21 | ||
|
|
a245605152 | ||
|
|
f4a53209f4 | ||
|
|
877251bcae |
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "9.0.11",
|
"version": "9.0.10",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
]
|
]
|
||||||
|
|||||||
15
.github/CODEOWNERS
vendored
15
.github/CODEOWNERS
vendored
@@ -1,11 +1,4 @@
|
|||||||
# Joshua must review all changes to bump_version and any files it touches
|
# Joshua must review all changes to deployment and build.sh
|
||||||
bump_version @joshuaboniface
|
.ci/* @joshuaboniface
|
||||||
.github/ISSUE_TEMPLATE @joshuaboniface
|
deployment/* @joshuaboniface
|
||||||
MediaBrowser.Common/MediaBrowser.Common.csproj @joshuaboniface
|
build.sh @joshuaboniface
|
||||||
Jellyfin.Data/Jellyfin.Data.csproj @joshuaboniface
|
|
||||||
MediaBrowser.Controller/MediaBrowser.Controller.csproj @joshuaboniface
|
|
||||||
MediaBrowser.Model/MediaBrowser.Model.csproj @joshuaboniface
|
|
||||||
Emby.Naming/Emby.Naming.csproj @joshuaboniface
|
|
||||||
src/Jellyfin.Extensions/Jellyfin.Extensions.csproj @joshuaboniface
|
|
||||||
# Core must approve all changes within the repo config
|
|
||||||
.github/ @jellyfin/core
|
|
||||||
|
|||||||
5
.github/ISSUE_TEMPLATE/issue report.yml
vendored
5
.github/ISSUE_TEMPLATE/issue report.yml
vendored
@@ -87,10 +87,7 @@ body:
|
|||||||
label: Jellyfin Server version
|
label: Jellyfin Server version
|
||||||
description: What version of Jellyfin are you using?
|
description: What version of Jellyfin are you using?
|
||||||
options:
|
options:
|
||||||
- 10.11.3
|
- 10.10.0+
|
||||||
- 10.11.2
|
|
||||||
- 10.11.1
|
|
||||||
- 10.11.0
|
|
||||||
- Master
|
- Master
|
||||||
- Unstable
|
- Unstable
|
||||||
- Older*
|
- Older*
|
||||||
|
|||||||
10
.github/workflows/ci-codeql-analysis.yml
vendored
10
.github/workflows/ci-codeql-analysis.yml
vendored
@@ -20,18 +20,18 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
|
uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
|
||||||
|
|||||||
16
.github/workflows/ci-compat.yml
vendored
16
.github/workflows/ci-compat.yml
vendored
@@ -11,13 +11,13 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -40,14 +40,14 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ jobs:
|
|||||||
dotnet build Jellyfin.Server -o ./out
|
dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
- name: Upload Head
|
- name: Upload Head
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -85,13 +85,13 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download abi-head
|
- name: Download abi-head
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
path: abi-head
|
path: abi-head
|
||||||
|
|
||||||
- name: Download abi-base
|
- name: Download abi-base
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
path: abi-base
|
path: abi-base
|
||||||
|
|||||||
24
.github/workflows/ci-openapi.yml
vendored
24
.github/workflows/ci-openapi.yml
vendored
@@ -16,18 +16,18 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
@@ -55,13 +55,13 @@ jobs:
|
|||||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
||||||
git checkout --progress --force $ANCESTOR_REF
|
git checkout --progress --force $ANCESTOR_REF
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '9.0.x'
|
dotnet-version: '9.0.x'
|
||||||
- name: Generate openapi.json
|
- name: Generate openapi.json
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
- name: Upload openapi.json
|
- name: Upload openapi.json
|
||||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
retention-days: 14
|
retention-days: 14
|
||||||
@@ -80,12 +80,12 @@ jobs:
|
|||||||
- openapi-base
|
- openapi-base
|
||||||
steps:
|
steps:
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
- name: Download openapi-base
|
- name: Download openapi-base
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-base
|
name: openapi-base
|
||||||
path: openapi-base
|
path: openapi-base
|
||||||
@@ -158,7 +158,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@@ -172,7 +172,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (unstable) into place
|
- name: Move openapi.json (unstable) into place
|
||||||
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
@@ -220,7 +220,7 @@ jobs:
|
|||||||
run: |-
|
run: |-
|
||||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||||
- name: Download openapi-head
|
- name: Download openapi-head
|
||||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@@ -234,7 +234,7 @@ jobs:
|
|||||||
strip_components: 1
|
strip_components: 1
|
||||||
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||||
- name: Move openapi.json (stable) into place
|
- name: Move openapi.json (stable) into place
|
||||||
uses: appleboy/ssh-action@823bd89e131d8d508129f9443cad5855e9ba96f0 # v1.2.4
|
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||||
with:
|
with:
|
||||||
host: "${{ secrets.REPO_HOST }}"
|
host: "${{ secrets.REPO_HOST }}"
|
||||||
username: "${{ secrets.REPO_USER }}"
|
username: "${{ secrets.REPO_USER }}"
|
||||||
|
|||||||
6
.github/workflows/ci-tests.yml
vendored
6
.github/workflows/ci-tests.yml
vendored
@@ -20,9 +20,9 @@ jobs:
|
|||||||
|
|
||||||
runs-on: "${{ matrix.os }}"
|
runs-on: "${{ matrix.os }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@2016bd2012dba4e32de620c46fe006a3ac9f0602 # v5.0.1
|
- uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: ${{ env.SDK_VERSION }}
|
dotnet-version: ${{ env.SDK_VERSION }}
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
--verbosity minimal
|
--verbosity minimal
|
||||||
|
|
||||||
- name: Merge code coverage results
|
- name: Merge code coverage results
|
||||||
uses: danielpalme/ReportGenerator-GitHub-Action@dcdfb6e704e87df6b2ed0cf123a6c9f69e364869 # v5.5.0
|
uses: danielpalme/ReportGenerator-GitHub-Action@9870ed167742d546b99962ff815fcc1098355ed8 # v5.4.17
|
||||||
with:
|
with:
|
||||||
reports: "**/coverage.cobertura.xml"
|
reports: "**/coverage.cobertura.xml"
|
||||||
targetdir: "merged/"
|
targetdir: "merged/"
|
||||||
|
|||||||
6
.github/workflows/commands.yml
vendored
6
.github/workflows/commands.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
@@ -40,11 +40,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: pull in script
|
- name: pull in script
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
4
.github/workflows/issue-template-check.yml
vendored
4
.github/workflows/issue-template-check.yml
vendored
@@ -10,11 +10,11 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
steps:
|
steps:
|
||||||
- name: pull in script
|
- name: pull in script
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
repository: jellyfin/jellyfin-triage-script
|
repository: jellyfin/jellyfin-triage-script
|
||||||
- name: install python
|
- name: install python
|
||||||
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
cache: 'pip'
|
cache: 'pip'
|
||||||
|
|||||||
4
.github/workflows/release-bump-version.yaml
vendored
4
.github/workflows/release-bump-version.yaml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
yq-version: v4.9.8
|
yq-version: v4.9.8
|
||||||
|
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<PackageVersion Include="Diacritics" Version="4.0.17" />
|
<PackageVersion Include="Diacritics" Version="4.0.17" />
|
||||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||||
<PackageVersion Include="FsCheck.Xunit" Version="3.3.2" />
|
<PackageVersion Include="FsCheck.Xunit" Version="3.3.1" />
|
||||||
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
|
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
|
||||||
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
||||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
||||||
@@ -26,33 +26,33 @@
|
|||||||
<PackageVersion Include="libse" Version="4.0.12" />
|
<PackageVersion Include="libse" Version="4.0.12" />
|
||||||
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
<PackageVersion Include="LrcParser" Version="2025.623.0" />
|
||||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.10" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
|
||||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||||
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
<PackageVersion Include="Morestachio" Version="5.0.1.631" />
|
||||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||||
@@ -62,13 +62,13 @@
|
|||||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
|
||||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
|
||||||
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
<PackageVersion Include="prometheus-net" Version="8.2.1" />
|
||||||
<PackageVersion Include="Polly" Version="8.6.5" />
|
<PackageVersion Include="Polly" Version="8.6.4" />
|
||||||
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
|
||||||
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
|
||||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
<PackageVersion Include="Serilog.Sinks.Async" Version="2.1.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Console" Version="6.1.1" />
|
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
|
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
|
||||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
@@ -84,11 +84,11 @@
|
|||||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
|
||||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.11" />
|
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.10" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="9.0.11" />
|
<PackageVersion Include="System.Text.Json" Version="9.0.10" />
|
||||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.11" />
|
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.10" />
|
||||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageVersion Include="z440.atl.core" Version="7.9.0" />
|
<PackageVersion Include="z440.atl.core" Version="7.6.0" />
|
||||||
<PackageVersion Include="TMDbLib" Version="2.3.0" />
|
<PackageVersion Include="TMDbLib" Version="2.3.0" />
|
||||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||||
@@ -96,4 +96,4 @@
|
|||||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
|
||||||
<PackageVersion Include="xunit" Version="2.9.3" />
|
<PackageVersion Include="xunit" Version="2.9.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Naming</PackageId>
|
<PackageId>Jellyfin.Naming</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -10,17 +10,12 @@ namespace Emby.Naming.TV
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static partial class SeasonPathParser
|
public static partial class SeasonPathParser
|
||||||
{
|
{
|
||||||
private static readonly Regex CleanNameRegex = new(@"[ ._\-\[\]]", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
[GeneratedRegex(@"^\s*((?<seasonnumber>(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
|
[GeneratedRegex(@"^\s*((?<seasonnumber>(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
|
||||||
private static partial Regex ProcessPre();
|
private static partial Regex ProcessPre();
|
||||||
|
|
||||||
[GeneratedRegex(@"^\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<seasonnumber>\d+?)(?=\d{3,4}p|[^\d]|$)(?!\s*[Ee]\d)(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
|
[GeneratedRegex(@"^\s*(?:[[시즌]*|[シーズン]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<seasonnumber>(?>\d+)(?!\s*[Ee]\d+))(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
|
||||||
private static partial Regex ProcessPost();
|
private static partial Regex ProcessPost();
|
||||||
|
|
||||||
[GeneratedRegex(@"[sS](\d{1,4})(?!\d|[eE]\d)(?=\.|_|-|\[|\]|\s|$)", RegexOptions.None)]
|
|
||||||
private static partial Regex SeasonPrefix();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attempts to parse season number from path.
|
/// Attempts to parse season number from path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -61,34 +56,44 @@ namespace Emby.Naming.TV
|
|||||||
bool supportSpecialAliases,
|
bool supportSpecialAliases,
|
||||||
bool supportNumericSeasonFolders)
|
bool supportNumericSeasonFolders)
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName(path);
|
string filename = Path.GetFileName(path);
|
||||||
|
filename = Regex.Replace(filename, "[ ._-]", string.Empty);
|
||||||
var seasonPrefixMatch = SeasonPrefix().Match(fileName);
|
|
||||||
if (seasonPrefixMatch.Success &&
|
|
||||||
int.TryParse(seasonPrefixMatch.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
|
||||||
{
|
|
||||||
return (val, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
string filename = CleanNameRegex.Replace(fileName, string.Empty);
|
|
||||||
|
|
||||||
if (parentFolderName is not null)
|
if (parentFolderName is not null)
|
||||||
{
|
{
|
||||||
var cleanParent = CleanNameRegex.Replace(parentFolderName, string.Empty);
|
parentFolderName = Regex.Replace(parentFolderName, "[ ._-]", string.Empty);
|
||||||
filename = filename.Replace(cleanParent, string.Empty, StringComparison.OrdinalIgnoreCase);
|
filename = filename.Replace(parentFolderName, string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportSpecialAliases &&
|
if (supportSpecialAliases)
|
||||||
(filename.Equals("specials", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
filename.Equals("extras", StringComparison.OrdinalIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
return (0, true);
|
if (string.Equals(filename, "specials", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return (0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(filename, "extras", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return (0, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (supportNumericSeasonFolders &&
|
if (supportNumericSeasonFolders)
|
||||||
int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out val))
|
|
||||||
{
|
{
|
||||||
return (val, true);
|
if (int.TryParse(filename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
|
{
|
||||||
|
return (val, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filename.Length > 0 && (filename[0] == 'S' || filename[0] == 's'))
|
||||||
|
{
|
||||||
|
var testFilename = filename.AsSpan()[1..];
|
||||||
|
|
||||||
|
if (int.TryParse(testFilename, NumberStyles.Integer, CultureInfo.InvariantCulture, out var val))
|
||||||
|
{
|
||||||
|
return (val, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var preMatch = ProcessPre().Match(filename);
|
var preMatch = ProcessPre().Match(filename);
|
||||||
|
|||||||
@@ -17,13 +17,6 @@ namespace Emby.Naming.TV
|
|||||||
[GeneratedRegex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))")]
|
[GeneratedRegex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))")]
|
||||||
private static partial Regex SeriesNameRegex();
|
private static partial Regex SeriesNameRegex();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Regex that matches titles with year in parentheses. Captures the title (which may be
|
|
||||||
/// numeric) before the year, i.e. turns "1923 (2022)" into "1923".
|
|
||||||
/// </summary>
|
|
||||||
[GeneratedRegex(@"(?<title>.+?)\s*\(\d{4}\)")]
|
|
||||||
private static partial Regex TitleWithYearRegex();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve information about series from path.
|
/// Resolve information about series from path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -34,20 +27,6 @@ namespace Emby.Naming.TV
|
|||||||
{
|
{
|
||||||
string seriesName = Path.GetFileName(path);
|
string seriesName = Path.GetFileName(path);
|
||||||
|
|
||||||
// First check if the filename matches a title with year pattern (handles numeric titles)
|
|
||||||
if (!string.IsNullOrEmpty(seriesName))
|
|
||||||
{
|
|
||||||
var titleWithYearMatch = TitleWithYearRegex().Match(seriesName);
|
|
||||||
if (titleWithYearMatch.Success)
|
|
||||||
{
|
|
||||||
seriesName = titleWithYearMatch.Groups["title"].Value.Trim();
|
|
||||||
return new SeriesInfo(path)
|
|
||||||
{
|
|
||||||
Name = seriesName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SeriesPathParserResult result = SeriesPathParser.Parse(options, path);
|
SeriesPathParserResult result = SeriesPathParser.Parse(options, path);
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -107,20 +107,10 @@ namespace Emby.Server.Implementations.AppBase
|
|||||||
|
|
||||||
private void CheckOrCreateMarker(string path, string markerName, bool recursive = false)
|
private void CheckOrCreateMarker(string path, string markerName, bool recursive = false)
|
||||||
{
|
{
|
||||||
string? otherMarkers = null;
|
var otherMarkers = GetMarkers(path, recursive).FirstOrDefault(e => Path.GetFileName(e) != markerName);
|
||||||
try
|
|
||||||
{
|
|
||||||
otherMarkers = GetMarkers(path, recursive).FirstOrDefault(e => !Path.GetFileName(e.AsSpan()).Equals(markerName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Error while checking for marker files, assume none exist and keep going
|
|
||||||
// TODO: add some logging
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherMarkers is not null)
|
if (otherMarkers is not null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Expected to find only {markerName} but found marker for {otherMarkers}.");
|
throw new InvalidOperationException($"Exepected to find only {markerName} but found marker for {otherMarkers}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var markerPath = Path.Combine(path, markerName);
|
var markerPath = Path.Combine(path, markerName);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using System.Linq;
|
|||||||
using System.Security;
|
using System.Security;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
@@ -261,7 +260,7 @@ namespace Emby.Server.Implementations.IO
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var targetFileInfo = FileSystemHelper.ResolveLinkTarget(fileInfo, returnFinalTarget: true);
|
var targetFileInfo = (FileInfo?)fileInfo.ResolveLinkTarget(returnFinalTarget: true);
|
||||||
if (targetFileInfo is not null)
|
if (targetFileInfo is not null)
|
||||||
{
|
{
|
||||||
result.Exists = targetFileInfo.Exists;
|
result.Exists = targetFileInfo.Exists;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
@@ -12,24 +11,28 @@ namespace Emby.Server.Implementations.Library;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class DotIgnoreIgnoreRule : IResolverIgnoreRule
|
public class DotIgnoreIgnoreRule : IResolverIgnoreRule
|
||||||
{
|
{
|
||||||
private static readonly bool IsWindows = OperatingSystem.IsWindows();
|
|
||||||
|
|
||||||
private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
|
private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
|
||||||
{
|
{
|
||||||
for (var current = directory; current is not null; current = current.Parent)
|
var ignoreFile = new FileInfo(Path.Join(directory.FullName, ".ignore"));
|
||||||
|
if (ignoreFile.Exists)
|
||||||
{
|
{
|
||||||
var ignorePath = Path.Join(current.FullName, ".ignore");
|
return ignoreFile;
|
||||||
if (File.Exists(ignorePath))
|
|
||||||
{
|
|
||||||
return new FileInfo(ignorePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
var parentDir = directory.Parent;
|
||||||
|
if (parentDir is null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FindIgnoreFile(parentDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnored(fileInfo, parent);
|
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent)
|
||||||
|
{
|
||||||
|
return IsIgnored(fileInfo, parent);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks whether or not the file is ignored.
|
/// Checks whether or not the file is ignored.
|
||||||
@@ -39,58 +42,65 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
|
|||||||
/// <returns>True if the file should be ignored.</returns>
|
/// <returns>True if the file should be ignored.</returns>
|
||||||
public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
|
public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
|
||||||
{
|
{
|
||||||
var searchDirectory = fileInfo.IsDirectory
|
if (fileInfo.IsDirectory)
|
||||||
? new DirectoryInfo(fileInfo.FullName)
|
{
|
||||||
: new DirectoryInfo(Path.GetDirectoryName(fileInfo.FullName) ?? string.Empty);
|
var dirIgnoreFile = FindIgnoreFile(new DirectoryInfo(fileInfo.FullName));
|
||||||
|
if (dirIgnoreFile is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(searchDirectory.FullName))
|
// Fast path in case the ignore files isn't a symlink and is empty
|
||||||
|
if (dirIgnoreFile.LinkTarget is null && dirIgnoreFile.Length == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore the directory only if the .ignore file is empty
|
||||||
|
// evaluate individual files otherwise
|
||||||
|
return string.IsNullOrWhiteSpace(GetFileContent(dirIgnoreFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentDirPath = Path.GetDirectoryName(fileInfo.FullName);
|
||||||
|
if (string.IsNullOrEmpty(parentDirPath))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignoreFile = FindIgnoreFile(searchDirectory);
|
var folder = new DirectoryInfo(parentDirPath);
|
||||||
|
var ignoreFile = FindIgnoreFile(folder);
|
||||||
if (ignoreFile is null)
|
if (ignoreFile is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast path in case the ignore files isn't a symlink and is empty
|
string ignoreFileString = GetFileContent(ignoreFile);
|
||||||
if (ignoreFile.LinkTarget is null && ignoreFile.Length == 0)
|
|
||||||
|
if (string.IsNullOrWhiteSpace(ignoreFileString))
|
||||||
{
|
{
|
||||||
// Ignore directory if we just have the file
|
// Ignore directory if we just have the file
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var content = GetFileContent(ignoreFile);
|
// If file has content, base ignoring off the content .gitignore-style rules
|
||||||
return string.IsNullOrWhiteSpace(content)
|
var ignoreRules = ignoreFileString.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|| CheckIgnoreRules(fileInfo.FullName, content, fileInfo.IsDirectory);
|
var ignore = new Ignore.Ignore();
|
||||||
|
ignore.Add(ignoreRules);
|
||||||
|
|
||||||
|
return ignore.IsIgnored(fileInfo.FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CheckIgnoreRules(string path, string ignoreFileContent, bool isDirectory)
|
private static string GetFileContent(FileInfo dirIgnoreFile)
|
||||||
{
|
{
|
||||||
// If file has content, base ignoring off the content .gitignore-style rules
|
dirIgnoreFile = (FileInfo?)dirIgnoreFile.ResolveLinkTarget(returnFinalTarget: true) ?? dirIgnoreFile;
|
||||||
var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
if (!dirIgnoreFile.Exists)
|
||||||
var ignore = new Ignore.Ignore();
|
|
||||||
ignore.Add(rules);
|
|
||||||
|
|
||||||
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
|
|
||||||
// See https://github.com/jellyfin/jellyfin/issues/15484
|
|
||||||
var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
|
|
||||||
|
|
||||||
// Add trailing slash for directories to match "folder/"
|
|
||||||
if (isDirectory)
|
|
||||||
{
|
{
|
||||||
pathToCheck = string.Concat(pathToCheck.AsSpan().TrimEnd('/'), "/");
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ignore.IsIgnored(pathToCheck);
|
using (var reader = dirIgnoreFile.OpenText())
|
||||||
}
|
{
|
||||||
|
return reader.ReadToEnd();
|
||||||
private static string GetFileContent(FileInfo ignoreFile)
|
}
|
||||||
{
|
|
||||||
ignoreFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile;
|
|
||||||
return ignoreFile.Exists
|
|
||||||
? File.ReadAllText(ignoreFile.FullName)
|
|
||||||
: string.Empty;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -457,12 +457,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_cache.TryRemove(child.Id, out _);
|
_cache.TryRemove(child.Id, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent is Folder folder)
|
|
||||||
{
|
|
||||||
folder.Children = null;
|
|
||||||
folder.UserData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReportItemRemoved(item, parent);
|
ReportItemRemoved(item, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1999,12 +1993,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent is Folder folder)
|
|
||||||
{
|
|
||||||
folder.Children = null;
|
|
||||||
folder.UserData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ItemAdded is not null)
|
if (ItemAdded is not null)
|
||||||
{
|
{
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
@@ -2162,12 +2150,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
_itemRepository.SaveItems(items, cancellationToken);
|
_itemRepository.SaveItems(items, cancellationToken);
|
||||||
|
|
||||||
if (parent is Folder folder)
|
|
||||||
{
|
|
||||||
folder.Children = null;
|
|
||||||
folder.UserData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ItemUpdated is not null)
|
if (ItemUpdated is not null)
|
||||||
{
|
{
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
|
|||||||
@@ -226,11 +226,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <inheritdoc />>
|
/// <inheritdoc />>
|
||||||
public MediaProtocol GetPathProtocol(string path)
|
public MediaProtocol GetPathProtocol(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
|
||||||
{
|
|
||||||
return MediaProtocol.File;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("Rtsp", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return MediaProtocol.Rtsp;
|
return MediaProtocol.Rtsp;
|
||||||
|
|||||||
@@ -369,16 +369,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
|||||||
// We need to only look at the name of this actual item (not parents)
|
// We need to only look at the name of this actual item (not parents)
|
||||||
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan());
|
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path.AsSpan()) : Path.GetFileName(item.ContainingFolderPath.AsSpan());
|
||||||
|
|
||||||
var tmdbid = justName.GetAttributeValue("tmdbid");
|
if (!justName.IsEmpty)
|
||||||
|
|
||||||
// If not in a mixed folder and ID not found in folder path, check filename
|
|
||||||
if (string.IsNullOrEmpty(tmdbid) && !item.IsInMixedFolder)
|
|
||||||
{
|
{
|
||||||
tmdbid = Path.GetFileName(item.Path.AsSpan()).GetAttributeValue("tmdbid");
|
// Check for TMDb id
|
||||||
|
var tmdbid = justName.GetAttributeValue("tmdbid");
|
||||||
|
item.TrySetProviderId(MetadataProvider.Tmdb, tmdbid);
|
||||||
}
|
}
|
||||||
|
|
||||||
item.TrySetProviderId(MetadataProvider.Tmdb, tmdbid);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.Path))
|
if (!string.IsNullOrEmpty(item.Path))
|
||||||
{
|
{
|
||||||
// Check for IMDb id - we use full media path, as we can assume that this will match in any use case (whether id in parent dir or in file name)
|
// Check for IMDb id - we use full media path, as we can assume that this will match in any use case (whether id in parent dir or in file name)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
|
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
|
||||||
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
|
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
|
||||||
"LabelIpAddressValue": "Dirección IP: {0}",
|
"LabelIpAddressValue": "Dirección IP: {0}",
|
||||||
"LabelRunningTimeValue": "Tiempo corriendo: {0}",
|
"LabelRunningTimeValue": "Tiempo de reproducción: {0}",
|
||||||
"Latest": "Recientes",
|
"Latest": "Recientes",
|
||||||
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
|
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
|
||||||
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
|
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
|
||||||
|
|||||||
@@ -137,5 +137,5 @@
|
|||||||
"TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.",
|
"TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.",
|
||||||
"TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht",
|
"TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht",
|
||||||
"CleanupUserDataTask": "Puhasta kasutajaandmed",
|
"CleanupUserDataTask": "Puhasta kasutajaandmed",
|
||||||
"CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud."
|
"CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mis pole enam vähemalt 90 päeva saadaval olnud."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"Collections": "Sammlungen",
|
"Collections": "Sammlungen",
|
||||||
"DeviceOfflineWithName": "{0} wurde getrennt",
|
"DeviceOfflineWithName": "{0} wurde getrennt",
|
||||||
"DeviceOnlineWithName": "{0} ist verbunden",
|
"DeviceOnlineWithName": "{0} ist verbunden",
|
||||||
"FailedLoginAttemptWithUserName": "Fählgschlagene Ameldeversuech vo {0}",
|
"FailedLoginAttemptWithUserName": "Fehlgeschlagener Anmeldeversuch von {0}",
|
||||||
"Favorites": "Favorite",
|
"Favorites": "Favorite",
|
||||||
"Folders": "Ordner",
|
"Folders": "Ordner",
|
||||||
"Genres": "Genre",
|
"Genres": "Genre",
|
||||||
|
|||||||
@@ -129,12 +129,5 @@
|
|||||||
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
|
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
|
||||||
"TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें",
|
"TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें",
|
||||||
"TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ",
|
"TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ",
|
||||||
"TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है",
|
"TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है"
|
||||||
"TaskExtractMediaSegments": "मीडिया सेगमेंट स्कैन",
|
|
||||||
"TaskExtractMediaSegmentsDescription": "मीडियासेगमेंट सक्षम प्लगइन्स से मीडिया सेगमेंट निकालता है या प्राप्त करता है।",
|
|
||||||
"TaskMoveTrickplayImages": "ट्रिकप्ले छवि स्थान माइग्रेट करें",
|
|
||||||
"TaskMoveTrickplayImagesDescription": "लाइब्रेरी सेटिंग्स के अनुसार मौजूदा ट्रिकप्ले फ़ाइलों को स्थानांतरित करता है।",
|
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "संग्रहों और प्लेलिस्टों से उन आइटमों को हटाता है जो अब मौजूद नहीं हैं।",
|
|
||||||
"TaskCleanCollectionsAndPlaylists": "संग्रह और प्लेलिस्ट साफ़ करें",
|
|
||||||
"CleanupUserDataTask": "यूज़र डेटा की सफाई करता है।"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,5 @@
|
|||||||
"TaskMoveTrickplayImages": "트릭플레이 이미지 위치 마이그레이션",
|
"TaskMoveTrickplayImages": "트릭플레이 이미지 위치 마이그레이션",
|
||||||
"TaskMoveTrickplayImagesDescription": "추출된 트릭플레이 이미지를 라이브러리 설정에 따라 이동합니다.",
|
"TaskMoveTrickplayImagesDescription": "추출된 트릭플레이 이미지를 라이브러리 설정에 따라 이동합니다.",
|
||||||
"TaskDownloadMissingLyrics": "누락된 가사 다운로드",
|
"TaskDownloadMissingLyrics": "누락된 가사 다운로드",
|
||||||
"TaskDownloadMissingLyricsDescription": "가사 다운로드",
|
"TaskDownloadMissingLyricsDescription": "가사 다운로드"
|
||||||
"CleanupUserDataTask": "사용자 데이터 정리 작업",
|
|
||||||
"CleanupUserDataTaskDescription": "최소 90일 이상 존재하지 않는 미디어에 대한 사용자 데이터(시청 상태, 즐겨찾기 등)를 정리합니다."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"HeaderNextUp": "Дараа нь",
|
"HeaderNextUp": "Дараа нь",
|
||||||
"HeaderContinueWatching": "Үргэлжлүүлэн үзэх",
|
"HeaderContinueWatching": "Үргэлжлүүлэн үзэх",
|
||||||
"Songs": "Дуунууд",
|
"Songs": "Дуунууд",
|
||||||
"Playlists": "Тоглуулах жагсаалтууд",
|
"Playlists": "Playlist-ууд",
|
||||||
"Movies": "Кинонууд",
|
"Movies": "Кинонууд",
|
||||||
"Latest": "Сүүлийн үеийн",
|
"Latest": "Сүүлийн үеийн",
|
||||||
"Genres": "Төрлүүд",
|
"Genres": "Төрлүүд",
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
"Forced": "Хүчээр",
|
"Forced": "Хүчээр",
|
||||||
"HeaderAlbumArtists": "Цомгийн уран бүтээлчид",
|
"HeaderAlbumArtists": "Цомгийн уран бүтээлчид",
|
||||||
"HeaderFavoriteAlbums": "Дуртай цомгууд",
|
"HeaderFavoriteAlbums": "Дуртай цомгууд",
|
||||||
"HeaderLiveTV": "Шууд ТВ",
|
"HeaderLiveTV": "Шууд",
|
||||||
"HeaderRecordingGroups": "Бичлэгийн бүлгүүд",
|
"HeaderRecordingGroups": "Бичлэгийн бүлгүүд",
|
||||||
"HearingImpaired": "Сонсголын бэрхшээлтэй",
|
"HearingImpaired": "Сонсголын бэрхшээлтэй",
|
||||||
"HomeVideos": "Үндсэн дүрсүүд",
|
"HomeVideos": "Үндсэн дүрсүүд",
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
"ScheduledTaskStartedWithName": "{0}-г эхлүүлэв",
|
"ScheduledTaskStartedWithName": "{0}-г эхлүүлэв",
|
||||||
"ServerNameNeedsToBeRestarted": "{0}-г дахин асаана уу",
|
"ServerNameNeedsToBeRestarted": "{0}-г дахин асаана уу",
|
||||||
"Shows": "Шоу",
|
"Shows": "Шоу",
|
||||||
"Sync": "Синхрончлох",
|
"Sync": "Дахин",
|
||||||
"System": "Систем",
|
"System": "Систем",
|
||||||
"TvShows": "ТВ нэвтрүүлгүүд",
|
"TvShows": "ТВ нэвтрүүлгүүд",
|
||||||
"Undefined": "Танисангүй",
|
"Undefined": "Танисангүй",
|
||||||
|
|||||||
@@ -132,10 +132,5 @@
|
|||||||
"TaskDownloadMissingLyrics": "उपलब्ध नसलेली गीतपट्टी (Lyrics) डाउनलोड करा",
|
"TaskDownloadMissingLyrics": "उपलब्ध नसलेली गीतपट्टी (Lyrics) डाउनलोड करा",
|
||||||
"TaskAudioNormalization": "ऑडिओ सामान्यीकरण",
|
"TaskAudioNormalization": "ऑडिओ सामान्यीकरण",
|
||||||
"TaskAudioNormalizationDescription": "ऑडिओ सामान्यीकरणाचा डाटा स्कॅन करतो.",
|
"TaskAudioNormalizationDescription": "ऑडिओ सामान्यीकरणाचा डाटा स्कॅन करतो.",
|
||||||
"TaskDownloadMissingLyricsDescription": "गाण्यांची गीतपट्टी (Lyrics) डाउनलोड करतो",
|
"TaskDownloadMissingLyricsDescription": "गाण्यांची गीतपट्टी (Lyrics) डाउनलोड करतो"
|
||||||
"TaskExtractMediaSegmentsDescription": "सक्रिय असलेल्या प्लगिनमधून मीडिया विभाग प्राप्त करते.",
|
|
||||||
"TaskMoveTrickplayImagesDescription": "लायब्ररीच्या सेटिंग्जप्रमाणे आधीपासून अस्तित्वात असलेल्या ट्रिकप्ले फाइल्सचे स्थान बदलते.",
|
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "जे संग्रह आणि प्लेलिस्ट आता अस्तित्वात नाहीत, त्यांमधील घटक हटवते.",
|
|
||||||
"CleanupUserDataTask": "वापरकर्ता डेटाची स्वच्छता प्रक्रिया",
|
|
||||||
"CleanupUserDataTaskDescription": "९० दिवसांहून अधिक काळ अनुपस्थित असलेल्या माध्यमांवरील सर्व वापरकर्ता माहिती (जसे पाहण्याची स्थिती, आवडी इ.) हटवते."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
@@ -134,8 +134,6 @@
|
|||||||
"TaskCleanCollectionsAndPlaylistsDescription": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਵਿੱਚੋਂ ਉਹ ਆਈਟਮ ਹਟਾਉਂਦਾ ਹੈ ਜੋ ਹੁਣ ਮੌਜੂਦ ਨਹੀਂ ਹਨ।",
|
"TaskCleanCollectionsAndPlaylistsDescription": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਵਿੱਚੋਂ ਉਹ ਆਈਟਮ ਹਟਾਉਂਦਾ ਹੈ ਜੋ ਹੁਣ ਮੌਜੂਦ ਨਹੀਂ ਹਨ।",
|
||||||
"TaskCleanCollectionsAndPlaylists": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਨੂੰ ਸਾਫ ਕਰੋ",
|
"TaskCleanCollectionsAndPlaylists": "ਕਲੈਕਸ਼ਨਾਂ ਅਤੇ ਪਲੇਲਿਸਟਾਂ ਨੂੰ ਸਾਫ ਕਰੋ",
|
||||||
"TaskAudioNormalization": "ਆਵਾਜ਼ ਸਧਾਰਣੀਕਰਨ",
|
"TaskAudioNormalization": "ਆਵਾਜ਼ ਸਧਾਰਣੀਕਰਨ",
|
||||||
"TaskRefreshTrickplayImagesDescription": "ਵੀਡੀਓ ਲਈ ਟ੍ਰਿਕਪਲੇ ਪ੍ਰੀਵਿਊ ਬਣਾਉਂਦਾ ਹੈ (ਜੇ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਚੁਣਿਆ ਗਿਆ ਹੈ)।",
|
"TaskRefreshTrickplayImagesDescription": "ਚਲ ਰਹੀ ਲਾਇਬ੍ਰੇਰੀਆਂ ਵਿੱਚ ਵੀਡੀਓਜ਼ ਲਈ ਟ੍ਰਿਕਪਲੇ ਪ੍ਰੀਵਿਊ ਬਣਾਉਂਦਾ ਹੈ।",
|
||||||
"TaskKeyframeExtractorDescription": "ਕੀ-ਫ੍ਰੇਮਜ਼ ਨੂੰ ਵੀਡੀਓ ਫਾਈਲਾਂ ਵਿੱਚੋਂ ਨਿਕਾਲਦਾ ਹੈ ਤਾਂ ਜੋ ਹੋਰ ਜ਼ਿਆਦਾ ਸਟਿਕ ਹੋਣ ਵਾਲੀਆਂ HLS ਪਲੇਲਿਸਟਾਂ ਬਣਾਈਆਂ ਜਾ ਸਕਣ। ਇਹ ਕੰਮ ਲੰਬੇ ਸਮੇਂ ਤੱਕ ਚੱਲ ਸਕਦਾ ਹੈ।",
|
"TaskKeyframeExtractorDescription": "ਕੀ-ਫ੍ਰੇਮਜ਼ ਨੂੰ ਵੀਡੀਓ ਫਾਈਲਾਂ ਵਿੱਚੋਂ ਨਿਕਾਲਦਾ ਹੈ ਤਾਂ ਜੋ ਹੋਰ ਜ਼ਿਆਦਾ ਸਟਿਕ ਹੋਣ ਵਾਲੀਆਂ HLS ਪਲੇਲਿਸਟਾਂ ਬਣਾਈਆਂ ਜਾ ਸਕਣ। ਇਹ ਕੰਮ ਲੰਬੇ ਸਮੇਂ ਤੱਕ ਚੱਲ ਸਕਦਾ ਹੈ।"
|
||||||
"CleanupUserDataTaskDescription": "ਘੱਟੋ-ਘੱਟ 90 ਦਿਨਾਂ ਤੋਂ ਮੌਜੂਦ ਨਾ ਹੋਣ ਵਾਲੇ ਮੀਡੀਆ ਤੋਂ ਸਾਰੇ ਉਪਭੋਗਤਾ ਡੇਟਾ (ਵਾਚ ਸਟੇਟ, ਮਨਪਸੰਦ ਸਟੇਟਸ ਆਦਿ) ਨੂੰ ਸਾਫ਼ ਕਰਦਾ ਹੈ।",
|
|
||||||
"CleanupUserDataTask": "ਯੂਜ਼ਰ ਡਾਟਾ ਸਾਫ਼ ਕਰਨ ਦਾ ਕੰਮ"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
"Collections": "Barrels",
|
"Collections": "Barrels",
|
||||||
"ItemAddedWithName": "{0} is now with yer treasure",
|
"ItemAddedWithName": "{0} is now with yer treasure",
|
||||||
"Default": "Normal-like",
|
"Default": "Normal-like",
|
||||||
"FailedLoginAttemptWithUserName": "Ye failed to enter from {0}",
|
"FailedLoginAttemptWithUserName": "Ye failed to get in, try from {0}",
|
||||||
"Favorites": "Finest Loot",
|
"Favorites": "Finest Loot",
|
||||||
"ItemRemovedWithName": "{0} was taken from yer treasure",
|
"ItemRemovedWithName": "{0} was taken from yer treasure",
|
||||||
"LabelIpAddressValue": "Ship's coordinates: {0}",
|
"LabelIpAddressValue": "Ship's coordinates: {0}",
|
||||||
@@ -113,10 +113,5 @@
|
|||||||
"TaskCleanCache": "Sweep the Cache Chest",
|
"TaskCleanCache": "Sweep the Cache Chest",
|
||||||
"TaskRefreshChapterImages": "Claim chapter portraits",
|
"TaskRefreshChapterImages": "Claim chapter portraits",
|
||||||
"TaskRefreshChapterImagesDescription": "Paints wee portraits fer videos that own chapters.",
|
"TaskRefreshChapterImagesDescription": "Paints wee portraits fer videos that own chapters.",
|
||||||
"TaskRefreshLibrary": "Scan the Treasure Trove",
|
"TaskRefreshLibrary": "Scan the Treasure Trove"
|
||||||
"TasksChannelsCategory": "Channels o' thy Internet",
|
|
||||||
"TaskRefreshTrickplayImages": "Summon the picture tricks",
|
|
||||||
"TaskRefreshTrickplayImagesDescription": "Summons picture trick previews for videos in ye enabled book roost",
|
|
||||||
"TaskUpdatePlugins": "Resummon yer Plugins",
|
|
||||||
"TaskCleanTranscode": "Swab Ye Transcode Directory"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
"Artists": "Artistas",
|
"Artists": "Artistas",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||||
"Books": "Livros",
|
"Books": "Livros",
|
||||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
"CameraImageUploadedFrom": "Uma nova imagem de câmara foi enviada a partir de {0}",
|
||||||
"Channels": "Canais",
|
"Channels": "Canais",
|
||||||
"ChapterNameValue": "Capítulo {0}",
|
"ChapterNameValue": "Capítulo {0}",
|
||||||
"Collections": "Coleções",
|
"Collections": "Coleções",
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
"TasksMaintenanceCategory": "Bảo Trì",
|
"TasksMaintenanceCategory": "Bảo Trì",
|
||||||
"VersionNumber": "Phiên Bản {0}",
|
"VersionNumber": "Phiên Bản {0}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} đã được thêm vào thư viện của bạn",
|
"ValueHasBeenAddedToLibrary": "{0} đã được thêm vào thư viện của bạn",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} đã kết thúc phát {1} trên {2}",
|
"UserStoppedPlayingItemWithValues": "{0} đã phát xong {1} trên {2}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} đang phát {1} trên {2}",
|
"UserStartedPlayingItemWithValues": "{0} đang phát {1} trên {2}",
|
||||||
"UserPolicyUpdatedWithName": "Chính sách người dùng đã được cập nhật cho {0}",
|
"UserPolicyUpdatedWithName": "Chính sách người dùng đã được cập nhật cho {0}",
|
||||||
"UserPasswordChangedWithName": "Mật khẩu đã được thay đổi cho người dùng {0}",
|
"UserPasswordChangedWithName": "Mật khẩu đã được thay đổi cho người dùng {0}",
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"HeaderFavoriteShows": "最愛的節目",
|
"HeaderFavoriteShows": "最愛的節目",
|
||||||
"HeaderFavoriteSongs": "最愛的歌曲",
|
"HeaderFavoriteSongs": "最愛的歌曲",
|
||||||
"HeaderLiveTV": "電視直播",
|
"HeaderLiveTV": "電視直播",
|
||||||
"HeaderNextUp": "繼續觀看",
|
"HeaderNextUp": "接著播放",
|
||||||
"HeaderRecordingGroups": "錄製組",
|
"HeaderRecordingGroups": "錄製組",
|
||||||
"HomeVideos": "家庭影片",
|
"HomeVideos": "家庭影片",
|
||||||
"Inherit": "繼承",
|
"Inherit": "繼承",
|
||||||
@@ -127,8 +127,8 @@
|
|||||||
"HearingImpaired": "聽力障礙",
|
"HearingImpaired": "聽力障礙",
|
||||||
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
|
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
|
||||||
"TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。",
|
"TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。",
|
||||||
"TaskExtractMediaSegments": "掃描媒體分段資訊",
|
"TaskExtractMediaSegments": "掃描媒體段落",
|
||||||
"TaskExtractMediaSegmentsDescription": "從允許MediaSegment 功能的插件中獲取媒體片段。",
|
"TaskExtractMediaSegmentsDescription": "從MediaSegment中被允許的插件獲取媒體段落。",
|
||||||
"TaskDownloadMissingLyrics": "下載欠缺歌詞",
|
"TaskDownloadMissingLyrics": "下載欠缺歌詞",
|
||||||
"TaskDownloadMissingLyricsDescription": "下載歌詞",
|
"TaskDownloadMissingLyricsDescription": "下載歌詞",
|
||||||
"TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單",
|
"TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單",
|
||||||
@@ -137,6 +137,5 @@
|
|||||||
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
||||||
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
|
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
|
||||||
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
|
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
|
||||||
"CleanupUserDataTask": "用戶資料清理工作",
|
"CleanupUserDataTask": "用戶資料清理工作"
|
||||||
"CleanupUserDataTaskDescription": "從用戶數據中清除已經被刪除超過 90 日的媒體相關資料。"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,6 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
|
|
||||||
// Update the playlist in the repository
|
// Update the playlist in the repository
|
||||||
playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
|
playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
|
||||||
playlist.DateLastMediaAdded = DateTime.UtcNow;
|
|
||||||
|
|
||||||
await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
|
await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ public class ArtistsController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = null;
|
User? user = null;
|
||||||
@@ -325,6 +326,7 @@ public class ArtistsController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = null;
|
User? user = null;
|
||||||
@@ -465,7 +467,7 @@ public class ArtistsController : BaseJellyfinApiController
|
|||||||
public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
var item = _libraryManager.GetArtist(name, dtoOptions);
|
var item = _libraryManager.GetArtist(name, dtoOptions);
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class CollectionController : BaseJellyfinApiController
|
|||||||
UserIds = new[] { userId }
|
UserIds = new[] { userId }
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
|
var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ public class GenresController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = userId.IsNullOrEmpty()
|
User? user = userId.IsNullOrEmpty()
|
||||||
@@ -158,7 +159,8 @@ public class GenresController : BaseJellyfinApiController
|
|||||||
public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
|
public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions()
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
Genre? item;
|
Genre? item;
|
||||||
if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase))
|
if (genreName.Contains(BaseItem.SlugChar, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -133,6 +134,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -176,6 +178,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -211,6 +214,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
? null
|
? null
|
||||||
: _userManager.GetUserById(userId.Value);
|
: _userManager.GetUserById(userId.Value);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromGenres(new[] { name }, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -254,6 +258,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -297,6 +302,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
@@ -379,6 +385,7 @@ public class InstantMixController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
|
||||||
return GetResult(items, user, limit, dtoOptions);
|
return GetResult(items, user, limit, dtoOptions);
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
if (includeItemTypes.Length == 1
|
if (includeItemTypes.Length == 1
|
||||||
@@ -848,6 +849,7 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
|
|
||||||
var parentIdGuid = parentId ?? Guid.Empty;
|
var parentIdGuid = parentId ?? Guid.Empty;
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var ancestorIds = Array.Empty<Guid>();
|
var ancestorIds = Array.Empty<Guid>();
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
item = parent;
|
item = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var items = themeItems
|
var items = themeItems
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@@ -260,7 +260,7 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
item = parent;
|
item = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var items = themeItems
|
var items = themeItems
|
||||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
@@ -496,7 +496,7 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
|
|
||||||
var baseItemDtos = new List<BaseItemDto>();
|
var baseItemDtos = new List<BaseItemDto>();
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
BaseItem? parent = item.GetParent();
|
BaseItem? parent = item.GetParent();
|
||||||
|
|
||||||
while (parent is not null)
|
while (parent is not null)
|
||||||
@@ -556,7 +556,7 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
items = items.Where(i => i.IsHidden == val).ToList();
|
items = items.Where(i => i.IsHidden == val).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions);
|
var resultArray = _dtoService.GetBaseItemDtos(items, dtoOptions);
|
||||||
return new QueryResult<BaseItemDto>(resultArray);
|
return new QueryResult<BaseItemDto>(resultArray);
|
||||||
}
|
}
|
||||||
@@ -747,7 +747,8 @@ public class LibraryController : BaseJellyfinApiController
|
|||||||
return new QueryResult<BaseItemDto>();
|
return new QueryResult<BaseItemDto>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields };
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
var program = item as IHasProgramAttributes;
|
var program = item as IHasProgramAttributes;
|
||||||
bool? isMovie = item is Movie || (program is not null && program.IsMovie) || item is Trailer;
|
bool? isMovie = item is Movie || (program is not null && program.IsMovie) || item is Trailer;
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var channelResult = _liveTvManager.GetInternalChannels(
|
var channelResult = _liveTvManager.GetInternalChannels(
|
||||||
@@ -241,7 +242,8 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions()
|
||||||
|
.AddClientFields(User);
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +297,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
return await _liveTvManager.GetRecordingsAsync(
|
return await _liveTvManager.GetRecordingsAsync(
|
||||||
@@ -441,7 +444,8 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions()
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||||
}
|
}
|
||||||
@@ -631,6 +635,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -685,6 +690,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = body.Fields ?? [] }
|
var dtoOptions = new DtoOptions { Fields = body.Fields ?? [] }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes ?? []);
|
.AddAdditionalDtoOptions(body.EnableImages, body.EnableUserData, body.ImageTypeLimit, body.EnableImageTypes ?? []);
|
||||||
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
return await _liveTvManager.GetPrograms(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -754,6 +760,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
};
|
};
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
return await _liveTvManager.GetRecommendedProgramsAsync(query, dtoOptions, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,8 @@ public class MoviesController : BaseJellyfinApiController
|
|||||||
var user = userId.IsNullOrEmpty()
|
var user = userId.IsNullOrEmpty()
|
||||||
? null
|
? null
|
||||||
: _userManager.GetUserById(userId.Value);
|
: _userManager.GetUserById(userId.Value);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields };
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
var categories = new List<RecommendationDto>();
|
var categories = new List<RecommendationDto>();
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ public class MusicGenresController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = userId.IsNullOrEmpty()
|
User? user = userId.IsNullOrEmpty()
|
||||||
@@ -147,7 +148,7 @@ public class MusicGenresController : BaseJellyfinApiController
|
|||||||
public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
|
public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
MusicGenre? item;
|
MusicGenre? item;
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = userId.IsNullOrEmpty()
|
User? user = userId.IsNullOrEmpty()
|
||||||
@@ -120,7 +121,8 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions()
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
var item = _libraryManager.GetPerson(name);
|
var item = _libraryManager.GetPerson(name);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
|
|||||||
@@ -548,6 +548,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public class StudiosController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = userId.IsNullOrEmpty()
|
User? user = userId.IsNullOrEmpty()
|
||||||
@@ -141,7 +142,7 @@ public class StudiosController : BaseJellyfinApiController
|
|||||||
public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
var item = _libraryManager.GetStudio(name);
|
var item = _libraryManager.GetStudio(name);
|
||||||
if (!userId.IsNullOrEmpty())
|
if (!userId.IsNullOrEmpty())
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class SuggestionsController : BaseJellyfinApiController
|
|||||||
user = _userManager.GetUserById(requestUserId);
|
user = _userManager.GetUserById(requestUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
|
var result = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
|
OrderBy = new[] { (ItemSortBy.Random, SortOrder.Descending) },
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ public class TvShowsController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var options = new DtoOptions { Fields = fields }
|
var options = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var result = _tvSeriesManager.GetNextUp(
|
var result = _tvSeriesManager.GetNextUp(
|
||||||
@@ -160,6 +161,7 @@ public class TvShowsController : BaseJellyfinApiController
|
|||||||
var parentIdGuid = parentId ?? Guid.Empty;
|
var parentIdGuid = parentId ?? Guid.Empty;
|
||||||
|
|
||||||
var options = new DtoOptions { Fields = fields }
|
var options = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
||||||
@@ -229,6 +231,7 @@ public class TvShowsController : BaseJellyfinApiController
|
|||||||
List<BaseItem> episodes;
|
List<BaseItem> episodes;
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
var shouldIncludeMissingEpisodes = (user is not null && user.DisplayMissingEpisodes) || User.GetIsApiKey();
|
var shouldIncludeMissingEpisodes = (user is not null && user.DisplayMissingEpisodes) || User.GetIsApiKey();
|
||||||
|
|
||||||
@@ -357,6 +360,7 @@ public class TvShowsController : BaseJellyfinApiController
|
|||||||
});
|
});
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
|
var returnItems = _dtoService.GetBaseItemDtos(seasons, dtoOptions, user);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
|
|
||||||
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
|
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var item = _libraryManager.GetUserRootFolder();
|
var item = _libraryManager.GetUserRootFolder();
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
|
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
|
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
|
||||||
|
|
||||||
return new QueryResult<BaseItemDto>(dtos);
|
return new QueryResult<BaseItemDto>(dtos);
|
||||||
@@ -422,7 +422,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
if (item is IHasTrailers hasTrailers)
|
if (item is IHasTrailers hasTrailers)
|
||||||
{
|
{
|
||||||
var trailers = hasTrailers.LocalTrailers;
|
var trailers = hasTrailers.LocalTrailers;
|
||||||
@@ -478,7 +478,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
return Ok(item
|
return Ok(item
|
||||||
.GetExtras()
|
.GetExtras()
|
||||||
@@ -549,6 +549,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var list = _userViewManager.GetLatestItems(
|
var list = _userViewManager.GetLatestItems(
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ public class UserViewsController : BaseJellyfinApiController
|
|||||||
|
|
||||||
var folders = _userViewManager.GetUserViews(query);
|
var folders = _userViewManager.GetUserViews(query);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId];
|
dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.PrimaryImageAspectRatio, ItemFields.DisplayPreferencesId];
|
||||||
|
|
||||||
var dtos = Array.ConvertAll(folders, i => _dtoService.GetBaseItemDto(i, dtoOptions, user));
|
var dtos = Array.ConvertAll(folders, i => _dtoService.GetBaseItemDto(i, dtoOptions, user));
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions();
|
||||||
|
dtoOptions = dtoOptions.AddClientFields(User);
|
||||||
|
|
||||||
BaseItemDto[] items;
|
BaseItemDto[] items;
|
||||||
if (item is Video video)
|
if (item is Video video)
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ public class YearsController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
|
.AddClientFields(User)
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
User? user = userId.IsNullOrEmpty()
|
User? user = userId.IsNullOrEmpty()
|
||||||
@@ -181,7 +182,8 @@ public class YearsController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions();
|
var dtoOptions = new DtoOptions()
|
||||||
|
.AddClientFields(User);
|
||||||
|
|
||||||
if (!userId.IsNullOrEmpty())
|
if (!userId.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Querying;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Extensions;
|
namespace Jellyfin.Api.Extensions;
|
||||||
|
|
||||||
@@ -9,6 +13,55 @@ namespace Jellyfin.Api.Extensions;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DtoExtensions
|
public static class DtoExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Add additional fields depending on client.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use in place of GetDtoOptions.
|
||||||
|
/// Legacy order: 2.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="dtoOptions">DtoOptions object.</param>
|
||||||
|
/// <param name="user">Current claims principal.</param>
|
||||||
|
/// <returns>Modified DtoOptions object.</returns>
|
||||||
|
internal static DtoOptions AddClientFields(
|
||||||
|
this DtoOptions dtoOptions, ClaimsPrincipal user)
|
||||||
|
{
|
||||||
|
string? client = user.GetClient();
|
||||||
|
|
||||||
|
// No client in claim
|
||||||
|
if (string.IsNullOrEmpty(client))
|
||||||
|
{
|
||||||
|
return dtoOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dtoOptions.ContainsField(ItemFields.RecursiveItemCount))
|
||||||
|
{
|
||||||
|
if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("classic", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.RecursiveItemCount];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dtoOptions.ContainsField(ItemFields.ChildCount))
|
||||||
|
{
|
||||||
|
if (client.Contains("kodi", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("wmc", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("media center", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("classic", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("roku", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("samsung", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
client.Contains("androidtv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
dtoOptions.Fields = [..dtoOptions.Fields, ItemFields.ChildCount];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dtoOptions;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add additional DtoOptions.
|
/// Add additional DtoOptions.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Data</PackageId>
|
<PackageId>Jellyfin.Data</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ public sealed class BaseItemRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
|
|
||||||
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
result.Items = dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
result.StartIndex = filter.StartIndex ?? 0;
|
result.StartIndex = filter.StartIndex ?? 0;
|
||||||
@@ -295,7 +294,6 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
|
|
||||||
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
return dbQuery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
}
|
}
|
||||||
@@ -339,8 +337,6 @@ public sealed class BaseItemRepository
|
|||||||
mainquery = ApplyGroupingFilter(context, mainquery, filter);
|
mainquery = ApplyGroupingFilter(context, mainquery, filter);
|
||||||
mainquery = ApplyQueryPaging(mainquery, filter);
|
mainquery = ApplyQueryPaging(mainquery, filter);
|
||||||
|
|
||||||
mainquery = ApplyNavigations(mainquery, filter);
|
|
||||||
|
|
||||||
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
return mainquery.AsEnumerable().Where(e => e is not null).Select(w => DeserializeBaseItem(w, filter.SkipDeserialization)).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +399,9 @@ public sealed class BaseItemRepository
|
|||||||
dbQuery = dbQuery.Distinct();
|
dbQuery = dbQuery.Distinct();
|
||||||
}
|
}
|
||||||
|
|
||||||
dbQuery = ApplyOrder(dbQuery, filter, context);
|
dbQuery = ApplyOrder(dbQuery, filter);
|
||||||
|
|
||||||
|
dbQuery = ApplyNavigations(dbQuery, filter);
|
||||||
|
|
||||||
return dbQuery;
|
return dbQuery;
|
||||||
}
|
}
|
||||||
@@ -448,7 +446,6 @@ public sealed class BaseItemRepository
|
|||||||
dbQuery = TranslateQuery(dbQuery, context, filter);
|
dbQuery = TranslateQuery(dbQuery, context, filter);
|
||||||
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
dbQuery = ApplyGroupingFilter(context, dbQuery, filter);
|
||||||
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
dbQuery = ApplyQueryPaging(dbQuery, filter);
|
||||||
dbQuery = ApplyNavigations(dbQuery, filter);
|
|
||||||
return dbQuery;
|
return dbQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1255,7 +1252,7 @@ public sealed class BaseItemRepository
|
|||||||
.AsSingleQuery()
|
.AsSingleQuery()
|
||||||
.Where(e => masterQuery.Contains(e.Id));
|
.Where(e => masterQuery.Contains(e.Id));
|
||||||
|
|
||||||
query = ApplyOrder(query, filter, context);
|
query = ApplyOrder(query, filter);
|
||||||
|
|
||||||
var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
|
var result = new QueryResult<(BaseItemDto, ItemCounts?)>();
|
||||||
if (filter.EnableTotalRecordCount)
|
if (filter.EnableTotalRecordCount)
|
||||||
@@ -1521,7 +1518,7 @@ public sealed class BaseItemRepository
|
|||||||
|| query.IncludeItemTypes.Contains(BaseItemKind.Season);
|
|| query.IncludeItemTypes.Contains(BaseItemKind.Season);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter, JellyfinDbContext context)
|
private IQueryable<BaseItemEntity> ApplyOrder(IQueryable<BaseItemEntity> query, InternalItemsQuery filter)
|
||||||
{
|
{
|
||||||
var orderBy = filter.OrderBy;
|
var orderBy = filter.OrderBy;
|
||||||
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
var hasSearch = !string.IsNullOrEmpty(filter.SearchTerm);
|
||||||
@@ -1540,7 +1537,7 @@ public sealed class BaseItemRepository
|
|||||||
var firstOrdering = orderBy.FirstOrDefault();
|
var firstOrdering = orderBy.FirstOrDefault();
|
||||||
if (firstOrdering != default)
|
if (firstOrdering != default)
|
||||||
{
|
{
|
||||||
var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter, context);
|
var expression = OrderMapper.MapOrderByField(firstOrdering.OrderBy, filter);
|
||||||
if (firstOrdering.SortOrder == SortOrder.Ascending)
|
if (firstOrdering.SortOrder == SortOrder.Ascending)
|
||||||
{
|
{
|
||||||
orderedQuery = query.OrderBy(expression);
|
orderedQuery = query.OrderBy(expression);
|
||||||
@@ -1565,7 +1562,7 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
foreach (var item in orderBy.Skip(1))
|
foreach (var item in orderBy.Skip(1))
|
||||||
{
|
{
|
||||||
var expression = OrderMapper.MapOrderByField(item.OrderBy, filter, context);
|
var expression = OrderMapper.MapOrderByField(item.OrderBy, filter);
|
||||||
if (item.SortOrder == SortOrder.Ascending)
|
if (item.SortOrder == SortOrder.Ascending)
|
||||||
{
|
{
|
||||||
orderedQuery = orderedQuery!.ThenBy(expression);
|
orderedQuery = orderedQuery!.ThenBy(expression);
|
||||||
@@ -1704,16 +1701,15 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(filter.SearchTerm))
|
if (!string.IsNullOrEmpty(filter.SearchTerm))
|
||||||
{
|
{
|
||||||
var cleanedSearchTerm = GetCleanValue(filter.SearchTerm);
|
var searchTerm = filter.SearchTerm.ToLower();
|
||||||
var originalSearchTerm = filter.SearchTerm.ToLower();
|
if (SearchWildcardTerms.Any(f => searchTerm.Contains(f)))
|
||||||
if (SearchWildcardTerms.Any(f => cleanedSearchTerm.Contains(f)))
|
|
||||||
{
|
{
|
||||||
cleanedSearchTerm = $"%{cleanedSearchTerm.Trim('%')}%";
|
searchTerm = $"%{searchTerm.Trim('%')}%";
|
||||||
baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!, cleanedSearchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle.ToLower(), originalSearchTerm)));
|
baseQuery = baseQuery.Where(e => EF.Functions.Like(e.CleanName!.ToLower(), searchTerm) || (e.OriginalTitle != null && EF.Functions.Like(e.OriginalTitle.ToLower(), searchTerm)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
baseQuery = baseQuery.Where(e => e.CleanName!.Contains(cleanedSearchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(originalSearchTerm)));
|
baseQuery = baseQuery.Where(e => e.CleanName!.ToLower().Contains(searchTerm) || (e.OriginalTitle != null && e.OriginalTitle.ToLower().Contains(searchTerm)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1948,20 +1944,19 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
|
if (!string.IsNullOrWhiteSpace(filter.NameStartsWith))
|
||||||
{
|
{
|
||||||
var startsWithLower = filter.NameStartsWith.ToLowerInvariant();
|
baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(filter.NameStartsWith));
|
||||||
baseQuery = baseQuery.Where(e => e.SortName!.StartsWith(startsWithLower));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
|
if (!string.IsNullOrWhiteSpace(filter.NameStartsWithOrGreater))
|
||||||
{
|
{
|
||||||
var startsOrGreaterLower = filter.NameStartsWithOrGreater.ToLowerInvariant();
|
// i hate this
|
||||||
baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(startsOrGreaterLower) >= 0);
|
baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() > filter.NameStartsWithOrGreater[0] || e.Name!.FirstOrDefault() > filter.NameStartsWithOrGreater[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
|
if (!string.IsNullOrWhiteSpace(filter.NameLessThan))
|
||||||
{
|
{
|
||||||
var lessThanLower = filter.NameLessThan.ToLowerInvariant();
|
// i hate this
|
||||||
baseQuery = baseQuery.Where(e => e.SortName!.CompareTo(lessThanLower ) < 0);
|
baseQuery = baseQuery.Where(e => e.SortName!.FirstOrDefault() < filter.NameLessThan[0] || e.Name!.FirstOrDefault() < filter.NameLessThan[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.ImageTypes.Length > 0)
|
if (filter.ImageTypes.Length > 0)
|
||||||
@@ -2420,34 +2415,39 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
if (filter.ExcludeInheritedTags.Length > 0)
|
if (filter.ExcludeInheritedTags.Length > 0)
|
||||||
{
|
{
|
||||||
baseQuery = baseQuery.Where(e =>
|
baseQuery = baseQuery
|
||||||
!e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
.Where(e => !e.ItemValues!.Where(w => w.ItemValue.Type == ItemValueType.InheritedTags || w.ItemValue.Type == ItemValueType.Tags)
|
||||||
&& (e.Type != _itemTypeLookup.BaseItemKindNames[BaseItemKind.Episode] || !e.SeriesId.HasValue ||
|
.Any(f => filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue)));
|
||||||
!context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && filter.ExcludeInheritedTags.Contains(f.ItemValue.CleanValue))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.IncludeInheritedTags.Length > 0)
|
if (filter.IncludeInheritedTags.Length > 0)
|
||||||
{
|
{
|
||||||
// For seasons and episodes, we also need to check the parent series' tags.
|
// Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client.
|
||||||
if (includeTypes.Any(t => t == BaseItemKind.Episode || t == BaseItemKind.Season))
|
// In addition to the tags for the episodes themselves, we need to manually query its parent (the season)'s tags as well.
|
||||||
|
if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
|
||||||
{
|
{
|
||||||
baseQuery = baseQuery.Where(e =>
|
baseQuery = baseQuery
|
||||||
e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
.Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags)
|
||||||
|| (e.SeriesId.HasValue && context.ItemValuesMap.Any(f => f.ItemId == e.SeriesId.Value && f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
|
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
||||||
|
||
|
||||||
|
(e.ParentId.HasValue && context.ItemValuesMap.Where(w => w.ItemId == e.ParentId.Value && (w.ItemValue.Type == ItemValueType.InheritedTags || w.ItemValue.Type == ItemValueType.Tags))
|
||||||
|
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))));
|
||||||
}
|
}
|
||||||
|
|
||||||
// A playlist should be accessible to its owner regardless of allowed tags.
|
// A playlist should be accessible to its owner regardless of allowed tags.
|
||||||
else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
|
else if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Playlist)
|
||||||
{
|
{
|
||||||
baseQuery = baseQuery.Where(e =>
|
baseQuery = baseQuery
|
||||||
e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
.Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags)
|
||||||
|| e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""));
|
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue))
|
||||||
|
|| e.Data!.Contains($"OwnerUserId\":\"{filter.User!.Id:N}\""));
|
||||||
// d ^^ this is stupid it hate this.
|
// d ^^ this is stupid it hate this.
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
baseQuery = baseQuery.Where(e =>
|
baseQuery = baseQuery
|
||||||
e.ItemValues!.Any(f => f.ItemValue.Type == ItemValueType.Tags && filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)));
|
.Where(e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.InheritedTags || f.ItemValue.Type == ItemValueType.Tags)
|
||||||
|
.Any(f => filter.IncludeInheritedTags.Contains(f.ItemValue.CleanValue)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
#pragma warning disable RS0030 // Do not use banned APIs
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Database.Implementations;
|
|
||||||
using Jellyfin.Database.Implementations.Entities;
|
using Jellyfin.Database.Implementations.Entities;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -21,50 +18,39 @@ public static class OrderMapper
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sortBy">Item property to sort by.</param>
|
/// <param name="sortBy">Item property to sort by.</param>
|
||||||
/// <param name="query">Context Query.</param>
|
/// <param name="query">Context Query.</param>
|
||||||
/// <param name="jellyfinDbContext">Context.</param>
|
|
||||||
/// <returns>Func to be executed later for sorting query.</returns>
|
/// <returns>Func to be executed later for sorting query.</returns>
|
||||||
public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query, JellyfinDbContext jellyfinDbContext)
|
public static Expression<Func<BaseItemEntity, object?>> MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
return (sortBy, query.User) switch
|
return sortBy switch
|
||||||
{
|
{
|
||||||
(ItemSortBy.AirTime, _) => e => e.SortName, // TODO
|
ItemSortBy.AirTime => e => e.SortName, // TODO
|
||||||
(ItemSortBy.Runtime, _) => e => e.RunTimeTicks,
|
ItemSortBy.Runtime => e => e.RunTimeTicks,
|
||||||
(ItemSortBy.Random, _) => e => EF.Functions.Random(),
|
ItemSortBy.Random => e => EF.Functions.Random(),
|
||||||
(ItemSortBy.DatePlayed, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.LastPlayedDate,
|
ItemSortBy.DatePlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.LastPlayedDate,
|
||||||
(ItemSortBy.PlayCount, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.PlayCount,
|
ItemSortBy.PlayCount => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.PlayCount,
|
||||||
(ItemSortBy.IsFavoriteOrLiked, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.IsFavorite,
|
ItemSortBy.IsFavoriteOrLiked => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.IsFavorite,
|
||||||
(ItemSortBy.IsFolder, _) => e => e.IsFolder,
|
ItemSortBy.IsFolder => e => e.IsFolder,
|
||||||
(ItemSortBy.IsPlayed, _) => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
|
ItemSortBy.IsPlayed => e => e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
|
||||||
(ItemSortBy.IsUnplayed, _) => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
|
ItemSortBy.IsUnplayed => e => !e.UserData!.FirstOrDefault(f => f.UserId.Equals(query.User!.Id))!.Played,
|
||||||
(ItemSortBy.DateLastContentAdded, _) => e => e.DateLastMediaAdded,
|
ItemSortBy.DateLastContentAdded => e => e.DateLastMediaAdded,
|
||||||
(ItemSortBy.Artist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
ItemSortBy.Artist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Artist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
||||||
(ItemSortBy.AlbumArtist, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
ItemSortBy.AlbumArtist => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.AlbumArtist).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
||||||
(ItemSortBy.Studio, _) => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
ItemSortBy.Studio => e => e.ItemValues!.Where(f => f.ItemValue.Type == ItemValueType.Studios).Select(f => f.ItemValue.CleanValue).FirstOrDefault(),
|
||||||
(ItemSortBy.OfficialRating, _) => e => e.InheritedParentalRatingValue,
|
ItemSortBy.OfficialRating => e => e.InheritedParentalRatingValue,
|
||||||
(ItemSortBy.SeriesSortName, _) => e => e.SeriesName,
|
// ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
|
||||||
(ItemSortBy.Album, _) => e => e.Album,
|
ItemSortBy.SeriesSortName => e => e.SeriesName,
|
||||||
(ItemSortBy.DateCreated, _) => e => e.DateCreated,
|
|
||||||
(ItemSortBy.PremiereDate, _) => e => (e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)),
|
|
||||||
(ItemSortBy.StartDate, _) => e => e.StartDate,
|
|
||||||
(ItemSortBy.Name, _) => e => e.CleanName,
|
|
||||||
(ItemSortBy.CommunityRating, _) => e => e.CommunityRating,
|
|
||||||
(ItemSortBy.ProductionYear, _) => e => e.ProductionYear,
|
|
||||||
(ItemSortBy.CriticRating, _) => e => e.CriticRating,
|
|
||||||
(ItemSortBy.VideoBitRate, _) => e => e.TotalBitrate,
|
|
||||||
(ItemSortBy.ParentIndexNumber, _) => e => e.ParentIndexNumber,
|
|
||||||
(ItemSortBy.IndexNumber, _) => e => e.IndexNumber,
|
|
||||||
(ItemSortBy.SeriesDatePlayed, not null) => e =>
|
|
||||||
jellyfinDbContext.BaseItems
|
|
||||||
.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
|
||||||
.Join(jellyfinDbContext.UserData.Where(w => w.UserId == query.User.Id && w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
|
|
||||||
.Max(f => f),
|
|
||||||
(ItemSortBy.SeriesDatePlayed, null) => e => jellyfinDbContext.BaseItems.Where(w => w.SeriesPresentationUniqueKey == e.PresentationUniqueKey)
|
|
||||||
.Join(jellyfinDbContext.UserData.Where(w => w.Played), f => f.Id, f => f.ItemId, (item, userData) => userData.LastPlayedDate)
|
|
||||||
.Max(f => f),
|
|
||||||
// ItemSortBy.SeriesDatePlayed => e => jellyfinDbContext.UserData
|
|
||||||
// .Where(u => u.Item!.SeriesPresentationUniqueKey == e.PresentationUniqueKey && u.Played)
|
|
||||||
// .Max(f => f.LastPlayedDate),
|
|
||||||
// ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
|
// ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
|
||||||
|
ItemSortBy.Album => e => e.Album,
|
||||||
|
ItemSortBy.DateCreated => e => e.DateCreated,
|
||||||
|
ItemSortBy.PremiereDate => e => (e.PremiereDate ?? (e.ProductionYear.HasValue ? DateTime.MinValue.AddYears(e.ProductionYear.Value - 1) : null)),
|
||||||
|
ItemSortBy.StartDate => e => e.StartDate,
|
||||||
|
ItemSortBy.Name => e => e.CleanName,
|
||||||
|
ItemSortBy.CommunityRating => e => e.CommunityRating,
|
||||||
|
ItemSortBy.ProductionYear => e => e.ProductionYear,
|
||||||
|
ItemSortBy.CriticRating => e => e.CriticRating,
|
||||||
|
ItemSortBy.VideoBitRate => e => e.TotalBitrate,
|
||||||
|
ItemSortBy.ParentIndexNumber => e => e.ParentIndexNumber,
|
||||||
|
ItemSortBy.IndexNumber => e => e.IndexNumber,
|
||||||
_ => e => e.SortName
|
_ => e => e.SortName
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace Jellyfin.Server.Implementations.StorageHelpers;
|
|||||||
public static class StorageHelper
|
public static class StorageHelper
|
||||||
{
|
{
|
||||||
private const long TwoGigabyte = 2_147_483_647L;
|
private const long TwoGigabyte = 2_147_483_647L;
|
||||||
|
private const long FiveHundredAndTwelveMegaByte = 536_870_911L;
|
||||||
private static readonly string[] _byteHumanizedSuffixes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
private static readonly string[] _byteHumanizedSuffixes = ["B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -23,8 +24,10 @@ public static class StorageHelper
|
|||||||
public static void TestCommonPathsForStorageCapacity(IApplicationPaths applicationPaths, ILogger logger)
|
public static void TestCommonPathsForStorageCapacity(IApplicationPaths applicationPaths, ILogger logger)
|
||||||
{
|
{
|
||||||
TestDataDirectorySize(applicationPaths.DataPath, logger, TwoGigabyte);
|
TestDataDirectorySize(applicationPaths.DataPath, logger, TwoGigabyte);
|
||||||
|
TestDataDirectorySize(applicationPaths.LogDirectoryPath, logger, FiveHundredAndTwelveMegaByte);
|
||||||
TestDataDirectorySize(applicationPaths.CachePath, logger, TwoGigabyte);
|
TestDataDirectorySize(applicationPaths.CachePath, logger, TwoGigabyte);
|
||||||
TestDataDirectorySize(applicationPaths.ProgramDataPath, logger, TwoGigabyte);
|
TestDataDirectorySize(applicationPaths.ProgramDataPath, logger, TwoGigabyte);
|
||||||
|
TestDataDirectorySize(applicationPaths.TempDirectory, logger, FiveHundredAndTwelveMegaByte);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -254,10 +254,10 @@ public class TrickplayManager : ITrickplayManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We support video backdrops, but we should not generate trickplay images for them
|
// We support video backdrops, but we should not generate trickplay images for them
|
||||||
var parentDirectory = Directory.GetParent(video.Path);
|
var parentDirectory = Directory.GetParent(mediaPath);
|
||||||
if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase))
|
if (parentDirectory is not null && string.Equals(parentDirectory.Name, "backdrops", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", video.Path, video.Id);
|
_logger.LogDebug("Ignoring backdrop media found at {Path} for item {ItemID}", mediaPath, video.Id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
// As long as jellyfin supports password-less users, we need this little block here to accommodate
|
// As long as jellyfin supports password-less users, we need this little block here to accommodate
|
||||||
if (string.IsNullOrEmpty(resolvedUser.Password) && string.IsNullOrEmpty(password))
|
if (!HasPassword(resolvedUser) && string.IsNullOrEmpty(password))
|
||||||
{
|
{
|
||||||
return Task.FromResult(new ProviderAuthenticationResult
|
return Task.FromResult(new ProviderAuthenticationResult
|
||||||
{
|
{
|
||||||
@@ -93,6 +93,10 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasPassword(User user)
|
||||||
|
=> !string.IsNullOrEmpty(user?.Password);
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task ChangePassword(User user, string newPassword)
|
public Task ChangePassword(User user, string newPassword)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,12 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
throw new AuthenticationException("User Account cannot login with this provider. The Normal provider for this user cannot be found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool HasPassword(User user)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task ChangePassword(User user, string newPassword)
|
public Task ChangePassword(User user, string newPassword)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -306,12 +306,15 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public UserDto GetUserDto(User user, string? remoteEndPoint = null)
|
public UserDto GetUserDto(User user, string? remoteEndPoint = null)
|
||||||
{
|
{
|
||||||
|
var hasPassword = GetAuthenticationProvider(user).HasPassword(user);
|
||||||
var castReceiverApplications = _serverConfigurationManager.Configuration.CastReceiverApplications;
|
var castReceiverApplications = _serverConfigurationManager.Configuration.CastReceiverApplications;
|
||||||
return new UserDto
|
return new UserDto
|
||||||
{
|
{
|
||||||
Name = user.Username,
|
Name = user.Username,
|
||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
ServerId = _appHost.SystemId,
|
ServerId = _appHost.SystemId,
|
||||||
|
HasPassword = hasPassword,
|
||||||
|
HasConfiguredPassword = hasPassword,
|
||||||
EnableAutoLogin = user.EnableAutoLogin,
|
EnableAutoLogin = user.EnableAutoLogin,
|
||||||
LastLoginDate = user.LastLoginDate,
|
LastLoginDate = user.LastLoginDate,
|
||||||
LastActivityDate = user.LastActivityDate,
|
LastActivityDate = user.LastActivityDate,
|
||||||
|
|||||||
@@ -33,11 +33,9 @@ using Microsoft.AspNetCore.Builder;
|
|||||||
using Microsoft.AspNetCore.Cors.Infrastructure;
|
using Microsoft.AspNetCore.Cors.Infrastructure;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
|
||||||
using Microsoft.OpenApi.Any;
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Interfaces;
|
using Microsoft.OpenApi.Interfaces;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.Swagger;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||||
|
|
||||||
@@ -261,8 +259,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
c.OperationFilter<FileRequestFilter>();
|
c.OperationFilter<FileRequestFilter>();
|
||||||
c.OperationFilter<ParameterObsoleteFilter>();
|
c.OperationFilter<ParameterObsoleteFilter>();
|
||||||
c.DocumentFilter<AdditionalModelFilter>();
|
c.DocumentFilter<AdditionalModelFilter>();
|
||||||
})
|
});
|
||||||
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Microsoft.OpenApi.Models;
|
|
||||||
using Swashbuckle.AspNetCore.Swagger;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// OpenApi provider with caching.
|
|
||||||
/// </summary>
|
|
||||||
internal sealed class CachingOpenApiProvider : ISwaggerProvider
|
|
||||||
{
|
|
||||||
private const string CacheKey = "openapi.json";
|
|
||||||
|
|
||||||
private static readonly MemoryCacheEntryOptions _cacheOptions = new() { SlidingExpiration = TimeSpan.FromMinutes(5) };
|
|
||||||
private static readonly SemaphoreSlim _lock = new(1, 1);
|
|
||||||
private static readonly TimeSpan _lockTimeout = TimeSpan.FromSeconds(1);
|
|
||||||
|
|
||||||
private readonly IMemoryCache _memoryCache;
|
|
||||||
private readonly SwaggerGenerator _swaggerGenerator;
|
|
||||||
private readonly SwaggerGeneratorOptions _swaggerGeneratorOptions;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="CachingOpenApiProvider"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="optionsAccessor">The options accessor.</param>
|
|
||||||
/// <param name="apiDescriptionsProvider">The api descriptions provider.</param>
|
|
||||||
/// <param name="schemaGenerator">The schema generator.</param>
|
|
||||||
/// <param name="memoryCache">The memory cache.</param>
|
|
||||||
public CachingOpenApiProvider(
|
|
||||||
IOptions<SwaggerGeneratorOptions> optionsAccessor,
|
|
||||||
IApiDescriptionGroupCollectionProvider apiDescriptionsProvider,
|
|
||||||
ISchemaGenerator schemaGenerator,
|
|
||||||
IMemoryCache memoryCache)
|
|
||||||
{
|
|
||||||
_swaggerGeneratorOptions = optionsAccessor.Value;
|
|
||||||
_swaggerGenerator = new SwaggerGenerator(_swaggerGeneratorOptions, apiDescriptionsProvider, schemaGenerator);
|
|
||||||
_memoryCache = memoryCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
|
|
||||||
{
|
|
||||||
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
|
|
||||||
{
|
|
||||||
return AdjustDocument(openApiDocument, host, basePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
var acquired = _lock.Wait(_lockTimeout);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_memoryCache.TryGetValue(CacheKey, out openApiDocument) && openApiDocument is not null)
|
|
||||||
{
|
|
||||||
return AdjustDocument(openApiDocument, host, basePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!acquired)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("OpenApi document is generating");
|
|
||||||
}
|
|
||||||
|
|
||||||
openApiDocument = _swaggerGenerator.GetSwagger(documentName);
|
|
||||||
_memoryCache.Set(CacheKey, openApiDocument, _cacheOptions);
|
|
||||||
return AdjustDocument(openApiDocument, host, basePath);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (acquired)
|
|
||||||
{
|
|
||||||
_lock.Release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private OpenApiDocument AdjustDocument(OpenApiDocument document, string? host, string? basePath)
|
|
||||||
{
|
|
||||||
document.Servers = _swaggerGeneratorOptions.Servers.Count != 0
|
|
||||||
? _swaggerGeneratorOptions.Servers
|
|
||||||
: string.IsNullOrEmpty(host) && string.IsNullOrEmpty(basePath)
|
|
||||||
? []
|
|
||||||
: [new OpenApiServer { Url = $"{host}{basePath}" }];
|
|
||||||
|
|
||||||
return document;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
<None Update="wwwroot\api-docs\swagger\custom.css">
|
<None Update="wwwroot\api-docs\swagger\custom.css">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="wwwroot\api-docs\jellyfin.svg">
|
<None Update="wwwroot\api-docs\banner-dark.svg">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="ServerSetupApp/index.mstemplate.html">
|
<None Update="ServerSetupApp/index.mstemplate.html">
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
|
|
||||||
namespace Jellyfin.Server.Migrations.Routines;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Migration to disable legacy authorization in the system config.
|
|
||||||
/// </summary>
|
|
||||||
[JellyfinMigration("2025-11-18T16:00:00", nameof(DisableLegacyAuthorization))]
|
|
||||||
public class DisableLegacyAuthorization : IAsyncMigrationRoutine
|
|
||||||
{
|
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="DisableLegacyAuthorization"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
|
||||||
public DisableLegacyAuthorization(IServerConfigurationManager serverConfigurationManager)
|
|
||||||
{
|
|
||||||
_serverConfigurationManager = serverConfigurationManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task PerformAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_serverConfigurationManager.Configuration.EnableLegacyAuthorization = false;
|
|
||||||
_serverConfigurationManager.SaveConfiguration();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -383,6 +383,8 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
baseItemIds.Clear();
|
||||||
|
|
||||||
foreach (var item in peopleCache)
|
foreach (var item in peopleCache)
|
||||||
{
|
{
|
||||||
operation.JellyfinDbContext.Peoples.Add(item.Value.Person);
|
operation.JellyfinDbContext.Peoples.Add(item.Value.Person);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
{
|
{
|
||||||
"Name": "Console",
|
"Name": "Console",
|
||||||
"Args": {
|
"Args": {
|
||||||
"outputTemplate": "[{Timestamp:HH:mm:ss.fff}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
|
"outputTemplate": "[{Timestamp:HH:mm:ss}] [{Level:u3}] [{ThreadId}] {SourceContext}: {Message:lj}{NewLine}{Exception}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -249,7 +249,6 @@ public sealed class SetupServer : IDisposable
|
|||||||
{
|
{
|
||||||
{ "isInReportingMode", _isUnhealthy },
|
{ "isInReportingMode", _isUnhealthy },
|
||||||
{ "retryValue", retryAfterValue },
|
{ "retryValue", retryAfterValue },
|
||||||
{ "version", typeof(Emby.Server.Implementations.ApplicationHost).Assembly.GetName().Version! },
|
|
||||||
{ "logs", startupLogEntries },
|
{ "logs", startupLogEntries },
|
||||||
{ "networkManagerReady", networkManager is not null },
|
{ "networkManagerReady", networkManager is not null },
|
||||||
{ "localNetworkRequest", networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress) }
|
{ "localNetworkRequest", networkManager is not null && context.Connection.RemoteIpAddress is not null && networkManager.IsInLocalNetwork(context.Connection.RemoteIpAddress) }
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
<header class="flex-row">
|
<header class="flex-row">
|
||||||
|
|
||||||
{{^IF isInReportingMode}}
|
{{^IF isInReportingMode}}
|
||||||
<p>Jellyfin Server {{version}} still starting. Please wait.</p>
|
<p>Jellyfin Server still starting. Please wait.</p>
|
||||||
{{#ELSE}}
|
{{#ELSE}}
|
||||||
<p>Jellyfin Server has encountered an error and was not able to start.</p>
|
<p>Jellyfin Server has encountered an error and was not able to start.</p>
|
||||||
{{/ELSE}}
|
{{/ELSE}}
|
||||||
|
|||||||
34
Jellyfin.Server/wwwroot/api-docs/banner-dark.svg
Normal file
34
Jellyfin.Server/wwwroot/api-docs/banner-dark.svg
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- ***** BEGIN LICENSE BLOCK *****
|
||||||
|
- Part of the Jellyfin project (https://jellyfin.media)
|
||||||
|
-
|
||||||
|
- All copyright belongs to the Jellyfin contributors; a full list can
|
||||||
|
- be found in the file CONTRIBUTORS.md
|
||||||
|
-
|
||||||
|
- This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||||
|
- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
|
||||||
|
- ***** END LICENSE BLOCK ***** -->
|
||||||
|
<svg id="banner-dark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1536 512">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="linear-gradient" x1="110.25" y1="213.3" x2="496.14" y2="436.09" gradientUnits="userSpaceOnUse">
|
||||||
|
<stop offset="0" stop-color="#aa5cc3"/>
|
||||||
|
<stop offset="1" stop-color="#00a4dc"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<title>banner-dark</title>
|
||||||
|
<g id="banner-dark">
|
||||||
|
<g id="banner-dark-icon">
|
||||||
|
<path id="inner-shape" d="M261.42,201.62c-20.44,0-86.24,119.29-76.2,139.43s142.48,19.92,152.4,0S281.86,201.63,261.42,201.62Z" fill="url(#linear-gradient)"/>
|
||||||
|
<path id="outer-shape" d="M261.42,23.3C199.83,23.3,1.57,382.73,31.8,443.43s429.34,60,459.24,0S323,23.3,261.42,23.3ZM411.9,390.76c-19.59,39.33-281.08,39.77-300.9,0S221.1,115.48,261.45,115.48,431.49,351.42,411.9,390.76Z" fill="url(#linear-gradient)"/>
|
||||||
|
</g>
|
||||||
|
<g id="jellyfin-light-outlines" style="isolation:isolate" transform="translate(43.8)">
|
||||||
|
<path d="M556.64,350.75a67,67,0,0,1-22.87-27.47,8.91,8.91,0,0,1-1.49-4.75,7.42,7.42,0,0,1,2.83-5.94,9.25,9.25,0,0,1,6.09-2.38c3.16,0,5.94,1.69,8.31,5.05a48.09,48.09,0,0,0,16.34,20.34,40.59,40.59,0,0,0,24,7.58q20.51,0,33.27-12.62t12.77-33.12V159a8.44,8.44,0,0,1,2.67-6.39,9.56,9.56,0,0,1,6.83-2.52,9,9,0,0,1,6.68,2.52,8.7,8.7,0,0,1,2.53,6.39v138.4a64.7,64.7,0,0,1-8.32,32.67,59,59,0,0,1-23,22.72Q608.62,361,589.9,361A57.21,57.21,0,0,1,556.64,350.75Z" fill="#fff"/>
|
||||||
|
<path d="M831.66,279.47a8.77,8.77,0,0,1-6.24,2.53H713.16q0,17.82,7.27,31.92a54.91,54.91,0,0,0,20.79,22.28q13.51,8.18,31.93,8.17a54,54,0,0,0,25.54-5.94,52.7,52.7,0,0,0,18.12-15.15,10,10,0,0,1,6.24-2.67,8.14,8.14,0,0,1,7.72,7.72,8.81,8.81,0,0,1-3,6.24,74.7,74.7,0,0,1-23.91,19A65.56,65.56,0,0,1,773.45,361q-22.87,0-40.4-9.8a69.51,69.51,0,0,1-27.32-27.48q-9.79-17.66-9.8-40.83,0-24.36,9.65-42.62t25.69-27.92a65.2,65.2,0,0,1,34.16-9.65A70,70,0,0,1,798.84,211a65.78,65.78,0,0,1,25.39,24.36q9.81,16,10.1,38A8.07,8.07,0,0,1,831.66,279.47ZM733.5,231.8Q718.8,243.68,714.64,266H815.92v-2.38A46.91,46.91,0,0,0,807,240.27a48.47,48.47,0,0,0-18.56-15.15,54,54,0,0,0-23-5.2Q748.2,219.92,733.5,231.8Z" fill="#fff"/>
|
||||||
|
<path d="M888.24,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,888.24,355.5Z" fill="#fff"/>
|
||||||
|
<path d="M956.55,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,956.55,355.5Z" fill="#fff"/>
|
||||||
|
<path d="M1122.86,206.11a8.7,8.7,0,0,1,2.53,6.39v131q0,23.44-9.21,40.09a61.58,61.58,0,0,1-25.54,25.25q-16.34,8.61-36.83,8.61a96.73,96.73,0,0,1-23.31-2.68,61.72,61.72,0,0,1-18-7.12q-6.24-3.87-6.24-8.62a17.94,17.94,0,0,1,.6-3,8.06,8.06,0,0,1,3-4.45,7.49,7.49,0,0,1,4.45-1.49,7.91,7.91,0,0,1,3.56.89q19,10.39,36.24,10.4,24.65,0,39.06-15.44t14.4-42.18V333.38a54.37,54.37,0,0,1-21.38,20,62.55,62.55,0,0,1-30.3,7.58q-25.83,0-39.2-15.45t-13.37-41.87V212.5a8.91,8.91,0,1,1,17.82,0V301q0,21.39,9.36,32.38t29.25,11a48,48,0,0,0,23.32-6.09,49.88,49.88,0,0,0,17.82-16,37.44,37.44,0,0,0,6.68-21.24V212.5a9,9,0,0,1,15.29-6.39Z" fill="#fff"/>
|
||||||
|
<path d="M1210.18,161.41q-5.21,6.24-5.2,17.23v30.59h33.27a8.19,8.19,0,0,1,5.79,2.38,8.26,8.26,0,0,1,0,11.88,8.22,8.22,0,0,1-5.79,2.37H1205V349.12a8.91,8.91,0,1,1-17.82,0V225.86h-21.68a7.83,7.83,0,0,1-5.94-2.52,8.21,8.21,0,0,1-2.37-5.79,8,8,0,0,1,2.37-6.09,8.33,8.33,0,0,1,5.94-2.23h21.68V178.64q0-18.7,10.84-29t29-10.24a46.1,46.1,0,0,1,15.45,2.52q7.13,2.53,7.12,8.17a8.07,8.07,0,0,1-2.37,5.94,7.37,7.37,0,0,1-5.35,2.37,18.81,18.81,0,0,1-6.53-1.48,42,42,0,0,0-10.4-1.78Q1215.37,155.18,1210.18,161.41ZM1276,180.87c-2.19-1.88-3.27-4.61-3.27-8.17v-3q0-5.34,3.41-8.17t9.36-2.82q11.88,0,11.88,11v3c0,3.56-1,6.29-3.12,8.17s-5.1,2.82-9.06,2.82S1278.14,182.75,1276,180.87Zm15.59,174.63a8.92,8.92,0,0,1-15.3-6.38V212.5a8.91,8.91,0,1,1,17.82,0V349.12A8.65,8.65,0,0,1,1291.56,355.5Z" fill="#fff"/>
|
||||||
|
<path d="M1452.53,218.88q12.92,16.2,12.92,42.92v87.32a8.4,8.4,0,0,1-2.67,6.38,8.8,8.8,0,0,1-6.24,2.53,8.64,8.64,0,0,1-8.91-8.91V262.69q0-19.31-9.65-31.33t-29.85-12a53.28,53.28,0,0,0-42.77,21.83,36.24,36.24,0,0,0-7.13,21.53v86.43a8.91,8.91,0,1,1-17.82,0V216.06a8.91,8.91,0,1,1,17.82,0V232.4q8-12.77,23-21.24A61.84,61.84,0,0,1,1412,202.7Q1439.61,202.7,1452.53,218.88Z" fill="#fff"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.7 KiB |
@@ -1,26 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="251" height="72" fill="none" viewBox="0 0 251 72">
|
|
||||||
<g clip-path="url(#a)">
|
|
||||||
<path fill="url(#b)"
|
|
||||||
d="M24.212 49.158C22.66 46.042 32.838 27.588 36 27.588c3.167.002 13.323 18.488 11.788 21.57-1.534 3.082-22.025 3.116-23.576 0" />
|
|
||||||
<path fill="url(#c)" fill-rule="evenodd"
|
|
||||||
d="M.482 64.995C-4.195 55.605 26.477 0 36 0c9.533 0 40.153 55.713 35.527 64.995s-66.368 9.39-71.045 0m12.254-8.148c3.064 6.152 43.518 6.084 46.548 0 3.03-6.086-17.032-42.586-23.275-42.586S9.671 50.694 12.736 56.847"
|
|
||||||
clip-rule="evenodd" />
|
|
||||||
<path fill="#fff"
|
|
||||||
d="M225.22 56c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.219-.218c-.054-.107-.054-.247-.054-.527V26.8c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.183c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v2.895a7.9 7.9 0 0 1 3.419-3.254q2.261-1.103 5.074-1.103 3.308 0 5.845 1.434a10.1 10.1 0 0 1 4.026 4.026q1.434 2.536 1.434 5.9V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.218c-.107.055-.247.055-.527.055h-5.625c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527V38.408q0-2.978-1.709-4.688-1.654-1.764-4.357-1.764-2.702 0-4.412 1.764-1.654 1.766-1.654 4.688V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm-11.54-33.363c-.28 0-.42 0-.527-.055a.5.5 0 0 1-.218-.218c-.055-.107-.055-.247-.055-.527v-6.121c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527v6.12c0 .28 0 .42-.054.528a.5.5 0 0 1-.219.218c-.107.055-.247.055-.527.055zm0 33.363c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.219c-.055-.107-.055-.247-.055-.527V26.8c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h5.624c.28 0 .42 0 .527.055a.5.5 0 0 1 .219.218c.054.107.054.247.054.527v28.4c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-16.712-.054c.107.054.247.054.527.054h5.625c.28 0 .42 0 .526-.054a.5.5 0 0 0 .219-.219c.055-.107.055-.247.055-.527V32.452h5.872c.28 0 .42 0 .527-.054a.5.5 0 0 0 .219-.219c.054-.107.054-.247.054-.527V26.8c0-.28 0-.42-.054-.527a.5.5 0 0 0-.219-.218c-.107-.055-.247-.055-.527-.055h-5.872v-.992q0-2.261 1.323-3.31 1.379-1.102 3.75-1.102.454 0 .939.044c.345.031.518.047.634-.004a.48.48 0 0 0 .241-.22c.061-.111.061-.274.061-.6V15.39c0-.304 0-.457-.061-.589a.7.7 0 0 0-.248-.284c-.122-.078-.261-.097-.537-.136a14.5 14.5 0 0 0-1.966-.126q-5.184 0-8.273 2.812t-3.088 7.942V26H186.53c-.3 0-.451 0-.58.05a.75.75 0 0 0-.296.205c-.091.104-.143.244-.248.526l-7.43 19.9-7.483-19.903c-.105-.28-.158-.42-.249-.524a.75.75 0 0 0-.296-.205c-.129-.049-.279-.049-.578-.049h-5.769c-.394 0-.591 0-.717.083a.5.5 0 0 0-.213.314c-.031.147.041.33.186.697L174.281 56l-.661 1.6q-.883 1.874-2.041 3.033-1.103 1.158-3.584 1.158-.883 0-1.875-.166a13 13 0 0 1-.73-.1c-.389-.066-.584-.099-.709-.053a.47.47 0 0 0-.26.22c-.066.116-.066.298-.066.663v4.329c0 .243 0 .365.045.481a.7.7 0 0 0 .189.266c.095.081.194.116.392.185q.684.24 1.47.351 1.158.22 2.371.22 4.246 0 7.059-2.426 2.867-2.37 4.577-6.728l10.517-26.58h5.72V55.2c0 .28 0 .42.055.527a.5.5 0 0 0 .218.219M154.363 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527V55.2c0 .28 0 .42-.055.527a.5.5 0 0 1-.218.219c-.107.054-.247.054-.527.054zm-11.621 0c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.219-.219c-.054-.107-.054-.247-.054-.527V15.054c0-.28 0-.42.054-.527a.5.5 0 0 1 .219-.219c.107-.054.247-.054.527-.054h5.624c.28 0 .42 0 .527.054a.5.5 0 0 1 .219.219c.054.107.054.247.054.527V55.2c0 .28 0 .42-.054.527a.5.5 0 0 1-.219.219c-.107.054-.247.054-.527.054zm-18.132.662q-4.632-.001-8.107-2.096a14.6 14.6 0 0 1-5.404-5.68q-1.93-3.585-1.93-7.942 0-4.522 1.93-7.996 1.985-3.53 5.349-5.57 3.42-2.04 7.61-2.04 4.688 0 7.942 2.04 3.253 1.986 4.963 5.294 1.71 3.309 1.709 7.335 0 .828-.11 1.654-.031.45-.12.841c-.037.165-.055.247-.115.33a.55.55 0 0 1-.208.168c-.095.04-.194.04-.393.04h-21.057q.33 3.309 2.537 5.294 2.205 1.986 5.459 1.985 2.482 0 4.191-1.047a8.2 8.2 0 0 0 2.206-1.986c.241-.316.362-.474.484-.542a.6.6 0 0 1 .352-.083c.139.006.296.083.608.236l4.269 2.094c.239.118.359.176.431.275a.52.52 0 0 1 .098.298c0 .122-.058.231-.172.45q-1.432 2.742-4.526 4.607-3.419 2.04-7.996 2.04m-.552-25.368q-2.702 0-4.687 1.654-1.93 1.6-2.537 4.577h14.118q-.22-2.757-2.151-4.466-1.875-1.765-4.743-1.765M90.801 56c-.28 0-.42 0-.527-.054a.5.5 0 0 1-.218-.218C90 55.62 90 55.48 90 55.2v-5.294c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.218c.107-.055.247-.055.527-.055h1.572q2.646 0 4.19-1.489 1.6-1.545 1.6-4.08V15.715c0-.28 0-.42.055-.527a.5.5 0 0 1 .218-.219c.107-.054.247-.054.527-.054h5.956c.28 0 .42 0 .527.054a.5.5 0 0 1 .218.219c.055.107.055.247.055.527v27.546q0 3.804-1.655 6.672-1.599 2.868-4.632 4.467-2.979 1.6-7.06 1.6z" />
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="b" x1="12" x2="71.999" y1="30.001" y2="63.002"
|
|
||||||
gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#aa5cc3" />
|
|
||||||
<stop offset="1" stop-color="#00a4dc" />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="c" x1="12" x2="71.999" y1="29.999" y2="63.001"
|
|
||||||
gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#aa5cc3" />
|
|
||||||
<stop offset="1" stop-color="#00a4dc" />
|
|
||||||
</linearGradient>
|
|
||||||
<clipPath id="a">
|
|
||||||
<path fill="#fff" d="M0 0h251v72H0z" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -4,14 +4,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.topbar-wrapper .link:after {
|
.topbar-wrapper .link:after {
|
||||||
content: '';
|
content: url(../banner-dark.svg);
|
||||||
display: block;
|
display: block;
|
||||||
background-image: url(../jellyfin.svg);
|
-moz-box-sizing: border-box;
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: contain;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 220px;
|
max-width: 100%;
|
||||||
height: 40px;
|
max-height: 100%;
|
||||||
|
width: 150px;
|
||||||
}
|
}
|
||||||
/* end logo */
|
/* end logo */
|
||||||
|
|||||||
@@ -103,11 +103,11 @@ namespace MediaBrowser.Common.Configuration
|
|||||||
void MakeSanityCheckOrThrow();
|
void MakeSanityCheckOrThrow();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks and creates the given path and adds it with a marker file if non existent.
|
/// Checks and creates the given path and adds it with a marker file if non existant.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="path">The path to check.</param>
|
/// <param name="path">The path to check.</param>
|
||||||
/// <param name="markerName">The common marker file name.</param>
|
/// <param name="markerName">The common marker file name.</param>
|
||||||
/// <param name="recursive">Check for other settings paths recursively.</param>
|
/// <param name="recursive">Check for other settings paths recursivly.</param>
|
||||||
void CreateAndCheckMarker(string path, string markerName, bool recursive = false);
|
void CreateAndCheckMarker(string path, string markerName, bool recursive = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Common</PackageId>
|
<PackageId>Jellyfin.Common</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ namespace MediaBrowser.Controller.Authentication
|
|||||||
|
|
||||||
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
|
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
|
||||||
|
|
||||||
|
bool HasPassword(User user);
|
||||||
|
|
||||||
Task ChangePassword(User user, string newPassword);
|
Task ChangePassword(User user, string newPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.IO;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaSegments;
|
using MediaBrowser.Controller.MediaSegments;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
@@ -1128,15 +1127,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
var protocol = item.PathProtocol;
|
var protocol = item.PathProtocol;
|
||||||
|
|
||||||
// Resolve the item path so everywhere we use the media source it will always point to
|
|
||||||
// the correct path even if symlinks are in use. Calling ResolveLinkTarget on a non-link
|
|
||||||
// path will return null, so it's safe to check for all paths.
|
|
||||||
var itemPath = item.Path;
|
|
||||||
if (protocol is MediaProtocol.File && FileSystemHelper.ResolveLinkTarget(itemPath, returnFinalTarget: true) is { Exists: true } linkInfo)
|
|
||||||
{
|
|
||||||
itemPath = linkInfo.FullName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var info = new MediaSourceInfo
|
var info = new MediaSourceInfo
|
||||||
{
|
{
|
||||||
Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
|
Id = item.Id.ToString("N", CultureInfo.InvariantCulture),
|
||||||
@@ -1144,7 +1134,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
|
MediaStreams = MediaSourceManager.GetMediaStreams(item.Id),
|
||||||
MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
|
MediaAttachments = MediaSourceManager.GetMediaAttachments(item.Id),
|
||||||
Name = GetMediaSourceName(item),
|
Name = GetMediaSourceName(item),
|
||||||
Path = enablePathSubstitution ? GetMappedPath(item, itemPath, protocol) : itemPath,
|
Path = enablePathSubstitution ? GetMappedPath(item, item.Path, protocol) : item.Path,
|
||||||
RunTimeTicks = item.RunTimeTicks,
|
RunTimeTicks = item.RunTimeTicks,
|
||||||
Container = item.Container,
|
Container = item.Container,
|
||||||
Size = item.Size,
|
Size = item.Size,
|
||||||
|
|||||||
@@ -729,7 +729,9 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
query.StartIndex = startIndex;
|
query.StartIndex = startIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
return PostFilterAndSort(items, query);
|
var result = PostFilterAndSort(items, query);
|
||||||
|
result.TotalRecordCount = totalCount;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is not UserRootFolder
|
if (this is not UserRootFolder
|
||||||
@@ -999,7 +1001,9 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
items = GetChildren(user, true, out totalItemCount, childQuery).Where(filter);
|
items = GetChildren(user, true, out totalItemCount, childQuery).Where(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return PostFilterAndSort(items, query);
|
var result = PostFilterAndSort(items, query);
|
||||||
|
result.TotalRecordCount = totalItemCount;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
|
protected QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items, InternalItemsQuery query)
|
||||||
@@ -1035,15 +1039,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
|
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var filteredItems = items as IReadOnlyList<BaseItem> ?? items.ToList();
|
return UserViewBuilder.SortAndPage(items, null, query, LibraryManager);
|
||||||
var result = UserViewBuilder.SortAndPage(filteredItems, null, query, LibraryManager);
|
|
||||||
|
|
||||||
if (query.EnableTotalRecordCount)
|
|
||||||
{
|
|
||||||
result.TotalRecordCount = filteredItems.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
|
private static IEnumerable<BaseItem> CollapseBoxSetItemsIfNeeded(
|
||||||
@@ -1056,49 +1052,12 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(items);
|
ArgumentNullException.ThrowIfNull(items);
|
||||||
|
|
||||||
if (!CollapseBoxSetItems(query, queryParent, user, configurationManager))
|
if (CollapseBoxSetItems(query, queryParent, user, configurationManager))
|
||||||
{
|
{
|
||||||
return items;
|
items = collectionManager.CollapseItemsWithinBoxSets(items, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = configurationManager.Configuration;
|
return items;
|
||||||
|
|
||||||
bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
|
|
||||||
bool collapseSeries = config.EnableGroupingShowsIntoCollections;
|
|
||||||
|
|
||||||
if (user is null || (collapseMovies && collapseSeries))
|
|
||||||
{
|
|
||||||
return collectionManager.CollapseItemsWithinBoxSets(items, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!collapseMovies && !collapseSeries)
|
|
||||||
{
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collapsibleItems = new List<BaseItem>();
|
|
||||||
var remainingItems = new List<BaseItem>();
|
|
||||||
|
|
||||||
foreach (var item in items)
|
|
||||||
{
|
|
||||||
if ((collapseMovies && item is Movie) || (collapseSeries && item is Series))
|
|
||||||
{
|
|
||||||
collapsibleItems.Add(item);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
remainingItems.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (collapsibleItems.Count == 0)
|
|
||||||
{
|
|
||||||
return remainingItems;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collapsedItems = collectionManager.CollapseItemsWithinBoxSets(collapsibleItems, user);
|
|
||||||
|
|
||||||
return collapsedItems.Concat(remainingItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CollapseBoxSetItems(
|
private static bool CollapseBoxSetItems(
|
||||||
@@ -1129,26 +1088,24 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
var param = query.CollapseBoxSetItems;
|
var param = query.CollapseBoxSetItems;
|
||||||
if (param.HasValue)
|
|
||||||
|
if (!param.HasValue)
|
||||||
{
|
{
|
||||||
return param.Value && AllowBoxSetCollapsing(query);
|
if (user is not null && query.IncludeItemTypes.Any(type =>
|
||||||
|
(type == BaseItemKind.Movie && !configurationManager.Configuration.EnableGroupingMoviesIntoCollections) ||
|
||||||
|
(type == BaseItemKind.Series && !configurationManager.Configuration.EnableGroupingShowsIntoCollections)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.IncludeItemTypes.Length == 0
|
||||||
|
|| query.IncludeItemTypes.Any(type => type == BaseItemKind.Movie || type == BaseItemKind.Series))
|
||||||
|
{
|
||||||
|
param = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var config = configurationManager.Configuration;
|
return param.HasValue && param.Value && AllowBoxSetCollapsing(query);
|
||||||
|
|
||||||
bool queryHasMovies = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie);
|
|
||||||
bool queryHasSeries = query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Series);
|
|
||||||
|
|
||||||
bool collapseMovies = config.EnableGroupingMoviesIntoCollections;
|
|
||||||
bool collapseSeries = config.EnableGroupingShowsIntoCollections;
|
|
||||||
|
|
||||||
if (user is not null)
|
|
||||||
{
|
|
||||||
bool canCollapse = (queryHasMovies && collapseMovies) || (queryHasSeries && collapseSeries);
|
|
||||||
return canCollapse && AllowBoxSetCollapsing(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (queryHasMovies || queryHasSeries) && AllowBoxSetCollapsing(query);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AllowBoxSetCollapsing(InternalItemsQuery request)
|
private static bool AllowBoxSetCollapsing(InternalItemsQuery request)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
@@ -62,82 +61,4 @@ public static class FileSystemHelper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the target of the specified file link.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="linkPath">The path of the file link.</param>
|
|
||||||
/// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next link.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="FileInfo"/> if the <paramref name="linkPath"/> is a link, regardless of if the target exists; otherwise, <c>null</c>.
|
|
||||||
/// </returns>
|
|
||||||
public static FileInfo? ResolveLinkTarget(string linkPath, bool returnFinalTarget = false)
|
|
||||||
{
|
|
||||||
// Check if the file exists so the native resolve handler won't throw at us.
|
|
||||||
if (!File.Exists(linkPath))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!returnFinalTarget)
|
|
||||||
{
|
|
||||||
return File.ResolveLinkTarget(linkPath, returnFinalTarget: false) as FileInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (File.ResolveLinkTarget(linkPath, returnFinalTarget: false) is not FileInfo targetInfo)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!targetInfo.Exists)
|
|
||||||
{
|
|
||||||
return targetInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
var currentPath = targetInfo.FullName;
|
|
||||||
var visited = new HashSet<string>(StringComparer.Ordinal) { linkPath, currentPath };
|
|
||||||
while (File.ResolveLinkTarget(currentPath, returnFinalTarget: false) is FileInfo linkInfo)
|
|
||||||
{
|
|
||||||
var targetPath = linkInfo.FullName;
|
|
||||||
|
|
||||||
// If an infinite loop is detected, return the file info for the
|
|
||||||
// first link in the loop we encountered.
|
|
||||||
if (!visited.Add(targetPath))
|
|
||||||
{
|
|
||||||
return new FileInfo(targetPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
targetInfo = linkInfo;
|
|
||||||
currentPath = targetPath;
|
|
||||||
|
|
||||||
// Exit if the target doesn't exist, so the native resolve handler won't throw at us.
|
|
||||||
if (!targetInfo.Exists)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return targetInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the target of the specified file link.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This helper exists because of this upstream runtime issue; https://github.com/dotnet/runtime/issues/92128.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="fileInfo">The file info of the file link.</param>
|
|
||||||
/// <param name="returnFinalTarget">true to follow links to the final target; false to return the immediate next link.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// A <see cref="FileInfo"/> if the <paramref name="fileInfo"/> is a link, regardless of if the target exists; otherwise, <c>null</c>.
|
|
||||||
/// </returns>
|
|
||||||
public static FileInfo? ResolveLinkTarget(FileInfo fileInfo, bool returnFinalTarget = false)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(fileInfo);
|
|
||||||
|
|
||||||
return ResolveLinkTarget(fileInfo.FullName, returnFinalTarget);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Controller</PackageId>
|
<PackageId>Jellyfin.Controller</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -154,12 +154,11 @@ namespace MediaBrowser.MediaEncoding.Probing
|
|||||||
|
|
||||||
info.Name = tags.GetFirstNotNullNorWhiteSpaceValue("title", "title-eng");
|
info.Name = tags.GetFirstNotNullNorWhiteSpaceValue("title", "title-eng");
|
||||||
info.ForcedSortName = tags.GetFirstNotNullNorWhiteSpaceValue("sort_name", "title-sort", "titlesort");
|
info.ForcedSortName = tags.GetFirstNotNullNorWhiteSpaceValue("sort_name", "title-sort", "titlesort");
|
||||||
info.Overview = tags.GetFirstNotNullNorWhiteSpaceValue("synopsis", "description", "desc", "comment");
|
info.Overview = tags.GetFirstNotNullNorWhiteSpaceValue("synopsis", "description", "desc");
|
||||||
|
|
||||||
|
info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort");
|
||||||
info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number");
|
info.ParentIndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "season_number");
|
||||||
info.IndexNumber = FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_sort") ??
|
info.ShowName = tags.GetValueOrDefault("show_name");
|
||||||
FFProbeHelpers.GetDictionaryNumericValue(tags, "episode_id");
|
|
||||||
info.ShowName = tags.GetValueOrDefault("show_name", "show");
|
|
||||||
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
|
info.ProductionYear = FFProbeHelpers.GetDictionaryNumericValue(tags, "date");
|
||||||
|
|
||||||
// Several different forms of retail/premiere date
|
// Several different forms of retail/premiere date
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
|||||||
ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath);
|
ArgumentException.ThrowIfNullOrEmpty(_mediaEncoder.EncoderPath);
|
||||||
|
|
||||||
// If subtitles get burned in fonts may need to be extracted from the media file
|
// If subtitles get burned in fonts may need to be extracted from the media file
|
||||||
if (state.SubtitleStream is not null && (state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode || state.BaseRequest.AlwaysBurnInSubtitleWhenTranscoding))
|
if (state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
||||||
{
|
{
|
||||||
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
|
if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -287,5 +287,5 @@ public class ServerConfiguration : BaseApplicationConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether old authorization methods are allowed.
|
/// Gets or sets a value indicating whether old authorization methods are allowed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool EnableLegacyAuthorization { get; set; }
|
public bool EnableLegacyAuthorization { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1250,37 +1250,30 @@ public class StreamInfo
|
|||||||
|
|
||||||
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
|
if (info.DeliveryMethod == SubtitleDeliveryMethod.External)
|
||||||
{
|
{
|
||||||
// Default to using the API URL
|
if (MediaSource.Protocol == MediaProtocol.File || !string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) || !stream.IsExternal)
|
||||||
info.Url = string.Format(
|
{
|
||||||
CultureInfo.InvariantCulture,
|
info.Url = string.Format(
|
||||||
"{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
CultureInfo.InvariantCulture,
|
||||||
baseUrl,
|
"{0}/Videos/{1}/{2}/Subtitles/{3}/{4}/Stream.{5}",
|
||||||
ItemId,
|
baseUrl,
|
||||||
MediaSourceId,
|
ItemId,
|
||||||
stream.Index.ToString(CultureInfo.InvariantCulture),
|
MediaSourceId,
|
||||||
startPositionTicks.ToString(CultureInfo.InvariantCulture),
|
stream.Index.ToString(CultureInfo.InvariantCulture),
|
||||||
subtitleProfile.Format);
|
startPositionTicks.ToString(CultureInfo.InvariantCulture),
|
||||||
info.IsExternalUrl = false; // Default to API URL
|
subtitleProfile.Format);
|
||||||
|
|
||||||
// Check conditions for potentially using the direct path
|
if (!string.IsNullOrEmpty(accessToken))
|
||||||
if (stream.IsExternal // Must be external
|
{
|
||||||
&& MediaSource?.Protocol != MediaProtocol.File // Main media must not be a local file
|
info.Url += "?ApiKey=" + accessToken;
|
||||||
&& string.Equals(stream.Codec, subtitleProfile.Format, StringComparison.OrdinalIgnoreCase) // Format must match (no conversion needed)
|
}
|
||||||
&& !string.IsNullOrEmpty(stream.Path) // Path must exist
|
|
||||||
&& Uri.TryCreate(stream.Path, UriKind.Absolute, out Uri? uriResult) // Path must be an absolute URI
|
info.IsExternalUrl = false;
|
||||||
&& (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps)) // Scheme must be HTTP or HTTPS
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// All conditions met, override with the direct path
|
|
||||||
info.Url = stream.Path;
|
info.Url = stream.Path;
|
||||||
info.IsExternalUrl = true;
|
info.IsExternalUrl = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append ApiKey only if we are using the API URL
|
|
||||||
if (!info.IsExternalUrl && !string.IsNullOrEmpty(accessToken))
|
|
||||||
{
|
|
||||||
// Use "?ApiKey=" as seen in HEAD and other parts of the code
|
|
||||||
info.Url += "?ApiKey=" + accessToken;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
using System;
|
using System;
|
||||||
using System.ComponentModel;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Users;
|
using MediaBrowser.Model.Users;
|
||||||
|
|
||||||
@@ -55,22 +54,20 @@ namespace MediaBrowser.Model.Dto
|
|||||||
/// Gets or sets a value indicating whether this instance has password.
|
/// Gets or sets a value indicating whether this instance has password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance has password; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance has password; otherwise, <c>false</c>.</value>
|
||||||
[Obsolete("This information is no longer provided")]
|
public bool HasPassword { get; set; }
|
||||||
public bool? HasPassword { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance has configured password.
|
/// Gets or sets a value indicating whether this instance has configured password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance has configured password; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance has configured password; otherwise, <c>false</c>.</value>
|
||||||
[Obsolete("This is always true")]
|
public bool HasConfiguredPassword { get; set; }
|
||||||
public bool? HasConfiguredPassword { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether this instance has configured easy password.
|
/// Gets or sets a value indicating whether this instance has configured easy password.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
|
/// <value><c>true</c> if this instance has configured easy password; otherwise, <c>false</c>.</value>
|
||||||
[Obsolete("Easy Password has been replaced with Quick Connect")]
|
[Obsolete("Easy Password has been replaced with Quick Connect")]
|
||||||
public bool? HasConfiguredEasyPassword { get; set; } = false;
|
public bool HasConfiguredEasyPassword { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether async login is enabled or not.
|
/// Gets or sets whether async login is enabled or not.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Model</PackageId>
|
<PackageId>Jellyfin.Model</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -151,10 +151,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
updateType |= beforeSaveResult;
|
updateType |= beforeSaveResult;
|
||||||
|
|
||||||
if (!isFirstRefresh)
|
updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false);
|
||||||
{
|
|
||||||
updateType = await SaveInternal(item, refreshOptions, updateType, isFirstRefresh, requiresRefresh, metadataResult, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next run metadata providers
|
// Next run metadata providers
|
||||||
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
|
||||||
@@ -320,8 +317,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
|
if (EnableUpdateMetadataFromChildren(item, isFullRefresh, updateType))
|
||||||
{
|
{
|
||||||
var children = GetChildrenForMetadataUpdates(item);
|
if (isFullRefresh || updateType > ItemUpdateType.None)
|
||||||
updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
|
{
|
||||||
|
var children = GetChildrenForMetadataUpdates(item);
|
||||||
|
|
||||||
|
updateType = UpdateMetadataFromChildren(item, children, isFullRefresh, updateType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var presentationUniqueKey = item.CreatePresentationUniqueKey();
|
var presentationUniqueKey = item.CreatePresentationUniqueKey();
|
||||||
@@ -343,10 +344,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
item.DateModified = info.LastWriteTimeUtc;
|
item.DateModified = info.LastWriteTimeUtc;
|
||||||
if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded)
|
if (ServerConfigurationManager.GetMetadataConfiguration().UseFileCreationTimeForDateAdded)
|
||||||
{
|
{
|
||||||
if (info.CreationTimeUtc > DateTime.MinValue)
|
item.DateCreated = info.CreationTimeUtc;
|
||||||
{
|
|
||||||
item.DateCreated = info.CreationTimeUtc;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is Video video)
|
if (item is Video video)
|
||||||
@@ -364,24 +362,16 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
protected virtual bool EnableUpdateMetadataFromChildren(TItemType item, bool isFullRefresh, ItemUpdateType currentUpdateType)
|
||||||
{
|
{
|
||||||
if (item is Folder folder)
|
if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
|
||||||
{
|
{
|
||||||
if (!isFullRefresh && currentUpdateType == ItemUpdateType.None)
|
if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosFromChildren || EnableUpdatingOfficialRatingFromChildren)
|
||||||
{
|
{
|
||||||
return folder.SupportsDateLastMediaAdded;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
|
if (item is Folder folder)
|
||||||
{
|
{
|
||||||
if (EnableUpdatingPremiereDateFromChildren || EnableUpdatingGenresFromChildren || EnableUpdatingStudiosFromChildren || EnableUpdatingOfficialRatingFromChildren)
|
return folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folder.SupportsDateLastMediaAdded || folder.SupportsCumulativeRunTimeTicks)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,42 +392,36 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
var updateType = ItemUpdateType.None;
|
var updateType = ItemUpdateType.None;
|
||||||
|
|
||||||
if (item is Folder folder)
|
if (isFullRefresh || currentUpdateType > ItemUpdateType.None)
|
||||||
{
|
{
|
||||||
if (folder.SupportsDateLastMediaAdded)
|
updateType |= UpdateCumulativeRunTimeTicks(item, children);
|
||||||
|
updateType |= UpdateDateLastMediaAdded(item, children);
|
||||||
|
|
||||||
|
// don't update user-changeable metadata for locked items
|
||||||
|
if (item.IsLocked)
|
||||||
{
|
{
|
||||||
updateType |= UpdateDateLastMediaAdded(item, children);
|
return updateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((isFullRefresh || currentUpdateType > ItemUpdateType.None) && folder.SupportsCumulativeRunTimeTicks)
|
if (EnableUpdatingPremiereDateFromChildren)
|
||||||
{
|
{
|
||||||
updateType |= UpdateCumulativeRunTimeTicks(item, children);
|
updateType |= UpdatePremiereDate(item, children);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!(isFullRefresh || currentUpdateType > ItemUpdateType.None) || item.IsLocked)
|
if (EnableUpdatingGenresFromChildren)
|
||||||
{
|
{
|
||||||
return updateType;
|
updateType |= UpdateGenres(item, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EnableUpdatingPremiereDateFromChildren)
|
if (EnableUpdatingStudiosFromChildren)
|
||||||
{
|
{
|
||||||
updateType |= UpdatePremiereDate(item, children);
|
updateType |= UpdateStudios(item, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EnableUpdatingGenresFromChildren)
|
if (EnableUpdatingOfficialRatingFromChildren)
|
||||||
{
|
{
|
||||||
updateType |= UpdateGenres(item, children);
|
updateType |= UpdateOfficialRating(item, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EnableUpdatingStudiosFromChildren)
|
|
||||||
{
|
|
||||||
updateType |= UpdateStudios(item, children);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (EnableUpdatingOfficialRatingFromChildren)
|
|
||||||
{
|
|
||||||
updateType |= UpdateOfficialRating(item, children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateType;
|
return updateType;
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||||||
{
|
{
|
||||||
Name = person.Name,
|
Name = person.Name,
|
||||||
Type = person.Type,
|
Type = person.Type,
|
||||||
Role = person.Role?.Trim()
|
Role = person.Role.Trim()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,15 +68,12 @@ namespace MediaBrowser.XbmcMetadata.Providers
|
|||||||
{
|
{
|
||||||
var file = GetXmlFile(new ItemInfo(item), directoryService);
|
var file = GetXmlFile(new ItemInfo(item), directoryService);
|
||||||
|
|
||||||
if (file?.Exists is not true)
|
if (file is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileTime = _fileSystem.GetLastWriteTimeUtc(file);
|
return file.Exists && _fileSystem.GetLastWriteTimeUtc(file) > item.DateLastSaved;
|
||||||
|
|
||||||
// 1 minute tolerance to avoid detecting our own file writes
|
|
||||||
return (fileTime - item.DateLastSaved) > TimeSpan.FromMinutes(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken);
|
protected abstract void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
[assembly: AssemblyVersion("10.12.0")]
|
[assembly: AssemblyVersion("10.11.2")]
|
||||||
[assembly: AssemblyFileVersion("10.12.0")]
|
[assembly: AssemblyFileVersion("10.11.2")]
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ public sealed class SqliteDatabaseProvider : IJellyfinDatabaseProvider
|
|||||||
sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
|
sqliteConnectionBuilder.DataSource = Path.Combine(_applicationPaths.DataPath, "jellyfin.db");
|
||||||
sqliteConnectionBuilder.Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default);
|
sqliteConnectionBuilder.Cache = GetOption(customOptions, "cache", Enum.Parse<SqliteCacheMode>, () => SqliteCacheMode.Default);
|
||||||
sqliteConnectionBuilder.Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true);
|
sqliteConnectionBuilder.Pooling = GetOption(customOptions, "pooling", e => e.Equals(bool.TrueString, StringComparison.OrdinalIgnoreCase), () => true);
|
||||||
sqliteConnectionBuilder.DefaultTimeout = GetOption(customOptions, "command-timeout", int.Parse, () => 30);
|
|
||||||
|
|
||||||
var connectionString = sqliteConnectionBuilder.ToString();
|
var connectionString = sqliteConnectionBuilder.ToString();
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,31 @@ namespace Jellyfin.Extensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class DictionaryExtensions
|
public static class DictionaryExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a string from a string dictionary, checking all keys sequentially,
|
||||||
|
/// stopping at the first key that returns a result that's neither null nor blank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary">The dictionary.</param>
|
||||||
|
/// <param name="key1">The first checked key.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
public static string? GetFirstNotNullNorWhiteSpaceValue(this IReadOnlyDictionary<string, string> dictionary, string key1)
|
||||||
|
{
|
||||||
|
return dictionary.GetFirstNotNullNorWhiteSpaceValue(key1, string.Empty, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a string from a string dictionary, checking all keys sequentially,
|
||||||
|
/// stopping at the first key that returns a result that's neither null nor blank.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dictionary">The dictionary.</param>
|
||||||
|
/// <param name="key1">The first checked key.</param>
|
||||||
|
/// <param name="key2">The second checked key.</param>
|
||||||
|
/// <returns>System.String.</returns>
|
||||||
|
public static string? GetFirstNotNullNorWhiteSpaceValue(this IReadOnlyDictionary<string, string> dictionary, string key1, string key2)
|
||||||
|
{
|
||||||
|
return dictionary.GetFirstNotNullNorWhiteSpaceValue(key1, key2, string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a string from a string dictionary, checking all keys sequentially,
|
/// Gets a string from a string dictionary, checking all keys sequentially,
|
||||||
/// stopping at the first key that returns a result that's neither null nor blank.
|
/// stopping at the first key that returns a result that's neither null nor blank.
|
||||||
@@ -15,9 +40,8 @@ namespace Jellyfin.Extensions
|
|||||||
/// <param name="key1">The first checked key.</param>
|
/// <param name="key1">The first checked key.</param>
|
||||||
/// <param name="key2">The second checked key.</param>
|
/// <param name="key2">The second checked key.</param>
|
||||||
/// <param name="key3">The third checked key.</param>
|
/// <param name="key3">The third checked key.</param>
|
||||||
/// <param name="key4">The fourth checked key.</param>
|
|
||||||
/// <returns>System.String.</returns>
|
/// <returns>System.String.</returns>
|
||||||
public static string? GetFirstNotNullNorWhiteSpaceValue(this IReadOnlyDictionary<string, string> dictionary, string key1, string? key2 = null, string? key3 = null, string? key4 = null)
|
public static string? GetFirstNotNullNorWhiteSpaceValue(this IReadOnlyDictionary<string, string> dictionary, string key1, string key2, string key3)
|
||||||
{
|
{
|
||||||
if (dictionary.TryGetValue(key1, out var val) && !string.IsNullOrWhiteSpace(val))
|
if (dictionary.TryGetValue(key1, out var val) && !string.IsNullOrWhiteSpace(val))
|
||||||
{
|
{
|
||||||
@@ -34,11 +58,6 @@ namespace Jellyfin.Extensions
|
|||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(key4) && dictionary.TryGetValue(key4, out val) && !string.IsNullOrWhiteSpace(val))
|
|
||||||
{
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Jellyfin Contributors</Authors>
|
<Authors>Jellyfin Contributors</Authors>
|
||||||
<PackageId>Jellyfin.Extensions</PackageId>
|
<PackageId>Jellyfin.Extensions</PackageId>
|
||||||
<VersionPrefix>10.12.0</VersionPrefix>
|
<VersionPrefix>10.11.2</VersionPrefix>
|
||||||
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
|
||||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -69,12 +69,6 @@ public class SeasonPathParserTests
|
|||||||
[InlineData("/media/YouTube/Devyn Johnston/2024-01-24 4070 Ti SUPER in under 7 minutes", "/media/YouTube/Devyn Johnston", null, false)]
|
[InlineData("/media/YouTube/Devyn Johnston/2024-01-24 4070 Ti SUPER in under 7 minutes", "/media/YouTube/Devyn Johnston", null, false)]
|
||||||
[InlineData("/media/YouTube/Devyn Johnston/2025-01-28 5090 vs 2 SFF Cases", "/media/YouTube/Devyn Johnston", null, false)]
|
[InlineData("/media/YouTube/Devyn Johnston/2025-01-28 5090 vs 2 SFF Cases", "/media/YouTube/Devyn Johnston", null, false)]
|
||||||
[InlineData("/Drive/202401244070", "/Drive", null, false)]
|
[InlineData("/Drive/202401244070", "/Drive", null, false)]
|
||||||
[InlineData("/Drive/Drive.S01.2160p.WEB-DL.DDP5.1.H.265-XXXX", "/Drive", 1, true)]
|
|
||||||
[InlineData("The Wonder Years/The.Wonder.Years.S04.1080p.PDTV.x264-JCH", "/The Wonder Years", 4, true)]
|
|
||||||
[InlineData("The Wonder Years/[The.Wonder.Years.S04.1080p.PDTV.x264-JCH]", "/The Wonder Years", 4, true)]
|
|
||||||
[InlineData("The Wonder Years/The.Wonder.Years [S04][1080p.PDTV.x264-JCH]", "/The Wonder Years", 4, true)]
|
|
||||||
[InlineData("The Wonder Years/The Wonder Years Season 01 1080p", "/The Wonder Years", 1, true)]
|
|
||||||
|
|
||||||
public void GetSeasonNumberFromPathTest(string path, string? parentPath, int? seasonNumber, bool isSeasonDirectory)
|
public void GetSeasonNumberFromPathTest(string path, string? parentPath, int? seasonNumber, bool isSeasonDirectory)
|
||||||
{
|
{
|
||||||
var result = SeasonPathParser.Parse(path, parentPath, true, true);
|
var result = SeasonPathParser.Parse(path, parentPath, true, true);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ namespace Jellyfin.Naming.Tests.TV
|
|||||||
[InlineData("/some/path/The Show", "The Show")]
|
[InlineData("/some/path/The Show", "The Show")]
|
||||||
[InlineData("/some/path/The Show s02e10 720p hdtv", "The Show")]
|
[InlineData("/some/path/The Show s02e10 720p hdtv", "The Show")]
|
||||||
[InlineData("/some/path/The Show s02e10 the episode 720p hdtv", "The Show")]
|
[InlineData("/some/path/The Show s02e10 the episode 720p hdtv", "The Show")]
|
||||||
[InlineData("/some/path/1923 (2022)", "1923")]
|
|
||||||
public void SeriesResolverResolveTest(string path, string name)
|
public void SeriesResolverResolveTest(string path, string name)
|
||||||
{
|
{
|
||||||
var res = SeriesResolver.Resolve(_namingOptions, path);
|
var res = SeriesResolver.Resolve(_namingOptions, path);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class OrderMapperTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ShouldReturnMappedOrderForSortingByPremierDate()
|
public void ShouldReturnMappedOrderForSortingByPremierDate()
|
||||||
{
|
{
|
||||||
var orderFunc = OrderMapper.MapOrderByField(ItemSortBy.PremiereDate, new InternalItemsQuery(), null!).Compile();
|
var orderFunc = OrderMapper.MapOrderByField(ItemSortBy.PremiereDate, new InternalItemsQuery()).Compile();
|
||||||
|
|
||||||
var expectedDate = new DateTime(1, 2, 3);
|
var expectedDate = new DateTime(1, 2, 3);
|
||||||
var expectedProductionYearDate = new DateTime(4, 1, 1);
|
var expectedProductionYearDate = new DateTime(4, 1, 1);
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
|
|||||||
var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
|
var users = await response.Content.ReadFromJsonAsync<UserDto[]>(_jsonOptions);
|
||||||
Assert.NotNull(users);
|
Assert.NotNull(users);
|
||||||
Assert.Single(users);
|
Assert.Single(users);
|
||||||
|
Assert.False(users![0].HasConfiguredPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -91,6 +92,8 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
|
|||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOptions);
|
var user = await response.Content.ReadFromJsonAsync<UserDto>(_jsonOptions);
|
||||||
Assert.Equal(TestUsername, user!.Name);
|
Assert.Equal(TestUsername, user!.Name);
|
||||||
|
Assert.False(user.HasPassword);
|
||||||
|
Assert.False(user.HasConfiguredPassword);
|
||||||
|
|
||||||
_testUserId = user.Id;
|
_testUserId = user.Id;
|
||||||
|
|
||||||
@@ -146,6 +149,12 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
|
|||||||
|
|
||||||
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
|
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
|
||||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
|
||||||
|
var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
|
||||||
|
await client.GetStreamAsync("Users"), _jsonOptions);
|
||||||
|
var user = users!.First(x => x.Id.Equals(_testUserId));
|
||||||
|
Assert.True(user.HasPassword);
|
||||||
|
Assert.True(user.HasConfiguredPassword);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -163,6 +172,12 @@ namespace Jellyfin.Server.Integration.Tests.Controllers
|
|||||||
|
|
||||||
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
|
using var response = await UpdateUserPassword(client, _testUserId, createRequest);
|
||||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||||
|
|
||||||
|
var users = await JsonSerializer.DeserializeAsync<UserDto[]>(
|
||||||
|
await client.GetStreamAsync("Users"), _jsonOptions);
|
||||||
|
var user = users!.First(x => x.Id.Equals(_testUserId));
|
||||||
|
Assert.False(user.HasPassword);
|
||||||
|
Assert.False(user.HasConfiguredPassword);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user