mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-08 19:32:05 +01:00
Compare commits
1 Commits
renovate/m
...
fix/ci-wor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d0b84ce48 |
@@ -3,7 +3,7 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"dotnet-ef": {
|
"dotnet-ef": {
|
||||||
"version": "10.0.5",
|
"version": "10.0.2",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-ef"
|
"dotnet-ef"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,31 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "Development Jellyfin Server",
|
"name": "Development Jellyfin Server",
|
||||||
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0-noble",
|
"image": "mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
|
||||||
"service": "app",
|
"service": "app",
|
||||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||||
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
|
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
|
||||||
"postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
|
"postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
|
||||||
// The previous way of installing extensions via the vs command dont work on selfhosted devcontainers
|
// reads the extensions list and installs them
|
||||||
"customizations": {
|
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
|
||||||
"vscode": {
|
|
||||||
"extensions": [
|
|
||||||
"ms-dotnettools.csharp",
|
|
||||||
"editorconfig.editorconfig",
|
|
||||||
"github.vscode-github-actions",
|
|
||||||
"ms-dotnettools.vscode-dotnet-runtime",
|
|
||||||
"ms-dotnettools.csdevkit",
|
|
||||||
"alexcvzz.vscode-sqlite",
|
|
||||||
"streetsidesoftware.code-spell-checker",
|
|
||||||
"eamodio.gitlens",
|
|
||||||
"redhat.vscode-xml"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/dotnet:2": {
|
"ghcr.io/devcontainers/features/dotnet:2": {
|
||||||
"version": "none",
|
"version": "none",
|
||||||
"dotnetRuntimeVersions": "10.0",
|
"dotnetRuntimeVersions": "9.0",
|
||||||
"aspNetCoreRuntimeVersions": "10.0"
|
"aspNetCoreRuntimeVersions": "9.0"
|
||||||
},
|
},
|
||||||
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
|
"ghcr.io/devcontainers-extra/features/apt-packages:1": {
|
||||||
"preserve_apt_list": false,
|
"preserve_apt_list": false,
|
||||||
|
|||||||
8
.github/ISSUE_TEMPLATE/issue report.yml
vendored
8
.github/ISSUE_TEMPLATE/issue report.yml
vendored
@@ -87,9 +87,13 @@ 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.8
|
|
||||||
- 10.11.7
|
|
||||||
- 10.11.6
|
- 10.11.6
|
||||||
|
- 10.11.5
|
||||||
|
- 10.11.4
|
||||||
|
- 10.11.3
|
||||||
|
- 10.11.2
|
||||||
|
- 10.11.1
|
||||||
|
- 10.11.0
|
||||||
- Master
|
- Master
|
||||||
- Unstable
|
- Unstable
|
||||||
- Older*
|
- Older*
|
||||||
|
|||||||
8
.github/workflows/ci-codeql-analysis.yml
vendored
8
.github/workflows/ci-codeql-analysis.yml
vendored
@@ -23,18 +23,18 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '10.0.x'
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
|
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
|
||||||
|
|||||||
55
.github/workflows/ci-compat-build.yml
vendored
Normal file
55
.github/workflows/ci-compat-build.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: ABI Compatibility Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
abi-head:
|
||||||
|
name: ABI - HEAD
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
|
- name: Upload Head
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: abi-head
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: out/
|
||||||
|
|
||||||
|
abi-base:
|
||||||
|
name: ABI - BASE
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build Jellyfin.Server -o ./out
|
||||||
|
|
||||||
|
- name: Upload Base
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: abi-base
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: out/
|
||||||
95
.github/workflows/ci-compat.yml
vendored
95
.github/workflows/ci-compat.yml
vendored
@@ -1,100 +1,37 @@
|
|||||||
name: ABI Compatibility
|
name: ABI Compatibility
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_run:
|
||||||
|
workflows: ["ABI Compatibility Build"]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
abi-head:
|
|
||||||
name: ABI - HEAD
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: read-all
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: '10.0.x'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
dotnet build Jellyfin.Server -o ./out
|
|
||||||
|
|
||||||
- name: Upload Head
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
name: abi-head
|
|
||||||
retention-days: 14
|
|
||||||
if-no-files-found: error
|
|
||||||
path: out/
|
|
||||||
|
|
||||||
abi-base:
|
|
||||||
name: ABI - BASE
|
|
||||||
if: ${{ github.base_ref != '' }}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: read-all
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup .NET
|
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: '10.0.x'
|
|
||||||
|
|
||||||
- name: Checkout common ancestor
|
|
||||||
env:
|
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
|
||||||
run: |
|
|
||||||
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
|
|
||||||
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
|
|
||||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
|
|
||||||
git checkout --progress --force $ANCESTOR_REF
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: |
|
|
||||||
dotnet build Jellyfin.Server -o ./out
|
|
||||||
|
|
||||||
- name: Upload Head
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
name: abi-base
|
|
||||||
retention-days: 14
|
|
||||||
if-no-files-found: error
|
|
||||||
path: out/
|
|
||||||
|
|
||||||
abi-diff:
|
abi-diff:
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write # to create or update comment (peter-evans/create-or-update-comment)
|
pull-requests: write
|
||||||
|
|
||||||
name: ABI - Difference
|
name: ABI - Difference
|
||||||
if: ${{ github.event_name == 'pull_request' }}
|
if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
|
||||||
- abi-head
|
|
||||||
- abi-base
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Download abi-head
|
- name: Download abi-head
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-head
|
name: abi-head
|
||||||
path: abi-head
|
path: abi-head
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Download abi-base
|
- name: Download abi-base
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: abi-base
|
name: abi-base
|
||||||
path: abi-base
|
path: abi-base
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Setup ApiCompat
|
- name: Setup ApiCompat
|
||||||
run: |
|
run: |
|
||||||
@@ -118,7 +55,7 @@ jobs:
|
|||||||
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||||
direction: last
|
direction: last
|
||||||
body-includes: abi-diff-workflow-comment
|
body-includes: abi-diff-workflow-comment
|
||||||
|
|
||||||
@@ -126,7 +63,7 @@ jobs:
|
|||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||||
if: ${{ steps.diff.outputs.body != '' }}
|
if: ${{ steps.diff.outputs.body != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
edit-mode: replace
|
edit-mode: replace
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
@@ -145,7 +82,7 @@ jobs:
|
|||||||
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
|
||||||
if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||||
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
comment-id: ${{ steps.find-comment.outputs.comment-id }}
|
||||||
edit-mode: replace
|
edit-mode: replace
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|||||||
55
.github/workflows/ci-openapi-build.yml
vendored
Normal file
55
.github/workflows/ci-openapi-build.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
name: OpenAPI Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
openapi-head:
|
||||||
|
name: OpenAPI - HEAD
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
|
- name: Generate openapi.json
|
||||||
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
|
|
||||||
|
- name: Upload openapi.json
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: openapi-head
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
|
||||||
|
|
||||||
|
openapi-base:
|
||||||
|
name: OpenAPI - BASE
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.pull_request.base.sha }}
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
|
with:
|
||||||
|
dotnet-version: '10.0.x'
|
||||||
|
|
||||||
|
- name: Generate openapi.json
|
||||||
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
|
|
||||||
|
- name: Upload openapi.json
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: openapi-base
|
||||||
|
retention-days: 1
|
||||||
|
if-no-files-found: error
|
||||||
|
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
|
||||||
@@ -1,35 +1,91 @@
|
|||||||
name: OpenAPI Publish
|
name: OpenAPI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["OpenAPI Build"]
|
||||||
|
types: [completed]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-openapi:
|
openapi-head:
|
||||||
name: OpenAPI - Publish Artifact
|
name: OpenAPI - HEAD
|
||||||
uses: ./.github/workflows/openapi-generate.yml
|
if: ${{ github.event_name == 'push' }}
|
||||||
permissions:
|
runs-on: ubuntu-latest
|
||||||
contents: read
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.sha }}
|
dotnet-version: '10.0.x'
|
||||||
repository: ${{ github.repository }}
|
|
||||||
artifact: openapi-head
|
- name: Generate openapi.json
|
||||||
|
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
|
||||||
|
|
||||||
|
- name: Upload openapi.json
|
||||||
|
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||||
|
with:
|
||||||
|
name: openapi-head
|
||||||
|
retention-days: 14
|
||||||
|
if-no-files-found: error
|
||||||
|
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
|
||||||
|
|
||||||
|
openapi-diff:
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
name: OpenAPI - Difference
|
||||||
|
if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download openapi-head
|
||||||
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
|
with:
|
||||||
|
name: openapi-head
|
||||||
|
path: openapi-head
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Download openapi-base
|
||||||
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
|
with:
|
||||||
|
name: openapi-base
|
||||||
|
path: openapi-base
|
||||||
|
run-id: ${{ github.event.workflow_run.id }}
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Detect OpenAPI changes
|
||||||
|
id: openapi-diff
|
||||||
|
uses: jellyfin/openapi-diff-action@9274f6bda9d01ab091942a4a8334baa53692e8a4 # v1.0.0
|
||||||
|
with:
|
||||||
|
old-spec: openapi-base/openapi.json
|
||||||
|
new-spec: openapi-head/openapi.json
|
||||||
|
markdown: openapi-changelog.md
|
||||||
|
add-pr-comment: true
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
pr-number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||||
|
|
||||||
|
|
||||||
publish-unstable:
|
publish-unstable:
|
||||||
name: OpenAPI - Publish Unstable Spec
|
name: OpenAPI - Publish Unstable Spec
|
||||||
if: ${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- publish-openapi
|
- openapi-head
|
||||||
steps:
|
steps:
|
||||||
- name: Set unstable dated version
|
- name: Set unstable dated version
|
||||||
id: version
|
id: version
|
||||||
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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
@@ -80,17 +136,17 @@ jobs:
|
|||||||
|
|
||||||
publish-stable:
|
publish-stable:
|
||||||
name: OpenAPI - Publish Stable Spec
|
name: OpenAPI - Publish Stable Spec
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- publish-openapi
|
- openapi-head
|
||||||
steps:
|
steps:
|
||||||
- name: Set version number
|
- name: Set version number
|
||||||
id: version
|
id: version
|
||||||
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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: openapi-head
|
name: openapi-head
|
||||||
path: openapi-head
|
path: openapi-head
|
||||||
4
.github/workflows/ci-tests.yml
vendored
4
.github/workflows/ci-tests.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
- uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.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@cf6fe1b38ed5becc89ffe056c1f240825993be5b # v5.5.4
|
uses: danielpalme/ReportGenerator-GitHub-Action@ee0ae774f6d3afedcbd1683c1ab21b83670bdf8e # v5.5.1
|
||||||
with:
|
with:
|
||||||
reports: "**/coverage.cobertura.xml"
|
reports: "**/coverage.cobertura.xml"
|
||||||
targetdir: "merged/"
|
targetdir: "merged/"
|
||||||
|
|||||||
2
.github/workflows/commands.yml
vendored
2
.github/workflows/commands.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
- edited
|
- edited
|
||||||
pull_request:
|
pull_request_target:
|
||||||
types:
|
types:
|
||||||
- labeled
|
- labeled
|
||||||
- synchronize
|
- synchronize
|
||||||
|
|||||||
2
.github/workflows/issue-stale.yml
vendored
2
.github/workflows/issue-stale.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
44
.github/workflows/openapi-generate.yml
vendored
44
.github/workflows/openapi-generate.yml
vendored
@@ -1,44 +0,0 @@
|
|||||||
name: OpenAPI Generate
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
ref:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
repository:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
artifact:
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
main:
|
|
||||||
name: Main
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
ref: ${{ inputs.ref }}
|
|
||||||
repository: ${{ inputs.repository }}
|
|
||||||
|
|
||||||
- name: Configure .NET
|
|
||||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
|
||||||
with:
|
|
||||||
dotnet-version: '10.0.x'
|
|
||||||
|
|
||||||
- name: Create File
|
|
||||||
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter Jellyfin.Server.Integration.Tests.OpenApiSpecTests
|
|
||||||
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
name: ${{ inputs.artifact }}
|
|
||||||
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
|
|
||||||
retention-days: 14
|
|
||||||
if-no-files-found: error
|
|
||||||
80
.github/workflows/openapi-pull-request.yml
vendored
80
.github/workflows/openapi-pull-request.yml
vendored
@@ -1,80 +0,0 @@
|
|||||||
name: OpenAPI Check
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ancestor:
|
|
||||||
name: Common Ancestor
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
base_ref: ${{ steps.ancestor.outputs.base_ref }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout Repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Search History
|
|
||||||
id: ancestor
|
|
||||||
run: |
|
|
||||||
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
|
|
||||||
git fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
|
|
||||||
|
|
||||||
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} HEAD)
|
|
||||||
|
|
||||||
echo "ref: ${ANCESTOR_REF}"
|
|
||||||
|
|
||||||
echo "base_ref=${ANCESTOR_REF}" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
head:
|
|
||||||
name: Head Artifact
|
|
||||||
uses: ./.github/workflows/openapi-generate.yml
|
|
||||||
with:
|
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
|
||||||
artifact: openapi-head
|
|
||||||
|
|
||||||
base:
|
|
||||||
name: Base Artifact
|
|
||||||
uses: ./.github/workflows/openapi-generate.yml
|
|
||||||
needs:
|
|
||||||
- ancestor
|
|
||||||
with:
|
|
||||||
ref: ${{ needs.ancestor.outputs.base_ref }}
|
|
||||||
repository: ${{ github.event.pull_request.base.repo.full_name }}
|
|
||||||
artifact: openapi-base
|
|
||||||
|
|
||||||
diff:
|
|
||||||
name: Generate Report
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs:
|
|
||||||
- head
|
|
||||||
- base
|
|
||||||
steps:
|
|
||||||
- name: Download Head
|
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
name: openapi-head
|
|
||||||
path: openapi-head
|
|
||||||
- name: Download Base
|
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
name: openapi-base
|
|
||||||
path: openapi-base
|
|
||||||
- name: Detect Changes
|
|
||||||
id: openapi-diff
|
|
||||||
run: |
|
|
||||||
sed -i 's:allOf:oneOf:g' openapi-head/openapi.json
|
|
||||||
sed -i 's:allOf:oneOf:g' openapi-base/openapi.json
|
|
||||||
|
|
||||||
mkdir -p /tmp/openapi-report
|
|
||||||
mv openapi-head/openapi.json /tmp/openapi-report/head.json
|
|
||||||
mv openapi-base/openapi.json /tmp/openapi-report/base.json
|
|
||||||
|
|
||||||
docker run -v /tmp/openapi-report:/data openapitools/openapi-diff:2.1.6 /data/base.json /data/head.json --state -l ERROR --markdown /data/openapi-report.md
|
|
||||||
- name: Upload Artifact
|
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
|
||||||
with:
|
|
||||||
name: openapi-report
|
|
||||||
path: /tmp/openapi-report/openapi-report.md
|
|
||||||
59
.github/workflows/openapi-workflow-run.yml
vendored
59
.github/workflows/openapi-workflow-run.yml
vendored
@@ -1,59 +0,0 @@
|
|||||||
name: OpenAPI Report
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows:
|
|
||||||
- OpenAPI Check
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
metadata:
|
|
||||||
name: Generate Metadata
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
||||||
outputs:
|
|
||||||
pr_number: ${{ steps.pr_number.outputs.pr_number }}
|
|
||||||
steps:
|
|
||||||
- name: Get Pull Request Number
|
|
||||||
id: pr_number
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
|
||||||
run: |
|
|
||||||
API_RESPONSE=$(gh pr list --repo "${GITHUB_REPOSITORY}" --search "${HEAD_SHA}" --state open --json number)
|
|
||||||
PR_NUMBER=$(echo "${API_RESPONSE}" | jq '.[0].number')
|
|
||||||
|
|
||||||
echo "repository: ${GITHUB_REPOSITORY}"
|
|
||||||
echo "sha: ${HEAD_SHA}"
|
|
||||||
echo "response: ${API_RESPONSE}"
|
|
||||||
echo "pr: ${PR_NUMBER}"
|
|
||||||
|
|
||||||
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
|
|
||||||
|
|
||||||
comment:
|
|
||||||
name: Pull Request Comment
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
||||||
needs:
|
|
||||||
- metadata
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Download OpenAPI Report
|
|
||||||
id: download_report
|
|
||||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
||||||
with:
|
|
||||||
name: openapi-report
|
|
||||||
path: openapi-report
|
|
||||||
run-id: ${{ github.event.workflow_run.id }}
|
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Push Comment
|
|
||||||
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
|
|
||||||
with:
|
|
||||||
github-token: ${{ secrets.JF_BOT_TOKEN }}
|
|
||||||
file-path: ${{ steps.download_report.outputs.download-path }}/openapi-report.md
|
|
||||||
pr-number: ${{ needs.metadata.outputs.pr_number }}
|
|
||||||
comment-tag: openapi-report
|
|
||||||
2
.github/workflows/project-automation.yml
vendored
2
.github/workflows/project-automation.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request_target:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|||||||
4
.github/workflows/pull-request-conflict.yml
vendored
4
.github/workflows/pull-request-conflict.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
pull_request_target:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Apply label
|
- name: Apply label
|
||||||
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
|
||||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request'}}
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
||||||
with:
|
with:
|
||||||
dirtyLabel: 'merge conflict'
|
dirtyLabel: 'merge conflict'
|
||||||
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
|
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
|
||||||
|
|||||||
2
.github/workflows/pull-request-stale.yaml
vendored
2
.github/workflows/pull-request-stale.yaml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
ascending: true
|
ascending: true
|
||||||
|
|||||||
2
.github/workflows/release-bump-version.yaml
vendored
2
.github/workflows/release-bump-version.yaml
vendored
@@ -28,7 +28,7 @@ jobs:
|
|||||||
timeoutSeconds: 3600
|
timeoutSeconds: 3600
|
||||||
|
|
||||||
- name: Setup YQ
|
- name: Setup YQ
|
||||||
uses: chrisdickinson/setup-yq@fa3192edd79d6eb0e4e12de8dde3a0c26f2b853b # latest
|
uses: chrisdickinson/setup-yq@latest
|
||||||
with:
|
with:
|
||||||
yq-version: v4.9.8
|
yq-version: v4.9.8
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,6 @@
|
|||||||
- [XVicarious](https://github.com/XVicarious)
|
- [XVicarious](https://github.com/XVicarious)
|
||||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||||
- [ZachPhelan](https://github.com/ZachPhelan)
|
- [ZachPhelan](https://github.com/ZachPhelan)
|
||||||
- [ZeusCraft10](https://github.com/ZeusCraft10)
|
|
||||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||||
- [Pusta](https://github.com/pusta)
|
- [Pusta](https://github.com/pusta)
|
||||||
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
- [nielsvanvelzen](https://github.com/nielsvanvelzen)
|
||||||
@@ -288,4 +287,3 @@
|
|||||||
- [Martin Reuter](https://github.com/reuterma24)
|
- [Martin Reuter](https://github.com/reuterma24)
|
||||||
- [Michael McElroy](https://github.com/mcmcelro)
|
- [Michael McElroy](https://github.com/mcmcelro)
|
||||||
- [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
|
- [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
|
||||||
- [DerMaddis](https://github.com/dermaddis)
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||||
<ItemGroup Label="Package Dependencies">
|
<ItemGroup Label="Package Dependencies">
|
||||||
<PackageVersion Include="AsyncKeyedLock" Version="8.0.2" />
|
<PackageVersion Include="AsyncKeyedLock" Version="8.0.0" />
|
||||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
||||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
||||||
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
|
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
|
||||||
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
|
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="coverlet.collector" Version="8.0.1" />
|
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
|
||||||
<PackageVersion Include="Diacritics" Version="4.1.4" />
|
<PackageVersion Include="Diacritics" Version="4.1.4" />
|
||||||
<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" />
|
||||||
@@ -26,28 +26,28 @@
|
|||||||
<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="8.0.1" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="8.0.1" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.3.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.0.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
|
||||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.5" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.4.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
<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" />
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<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.6" />
|
<PackageVersion Include="Polly" Version="8.6.5" />
|
||||||
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
|
<PackageVersion Include="Serilog.AspNetCore" Version="10.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" />
|
||||||
@@ -74,13 +74,13 @@
|
|||||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="[3.116.1]" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="[3.116.1]" />
|
||||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||||
<PackageVersion Include="Svg.Skia" Version="3.4.1" />
|
<PackageVersion Include="Svg.Skia" Version="3.2.1" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.9.0" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.2" />
|
||||||
<PackageVersion Include="System.Text.Json" Version="10.0.5" />
|
<PackageVersion Include="System.Text.Json" Version="10.0.2" />
|
||||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||||
<PackageVersion Include="z440.atl.core" Version="7.12.0" />
|
<PackageVersion Include="z440.atl.core" Version="7.10.0" />
|
||||||
<PackageVersion Include="TMDbLib" Version="3.0.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" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
|
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
CleanStrings =
|
CleanStrings =
|
||||||
[
|
[
|
||||||
@"^\s*(?<cleaned>.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multi|subs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS)(?=[ _\,\.\(\)\[\]\-]|$)",
|
@"^\s*(?<cleaned>.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multi|subs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
|
||||||
@"^\s*(?<cleaned>.+?)((\s*\[[^\]]+\]\s*)+)(\.[^\s]+)?$",
|
@"^(?<cleaned>.+?)(\[.*\])",
|
||||||
@"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)",
|
@"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)",
|
||||||
@"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)",
|
@"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)",
|
||||||
@"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$",
|
@"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$",
|
||||||
@@ -225,7 +225,6 @@ namespace Emby.Naming.Common
|
|||||||
".afc",
|
".afc",
|
||||||
".amf",
|
".amf",
|
||||||
".aif",
|
".aif",
|
||||||
".aifc",
|
|
||||||
".aiff",
|
".aiff",
|
||||||
".alac",
|
".alac",
|
||||||
".amr",
|
".amr",
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public static class TvParserHelpers
|
|||||||
/// <param name="status">The status string.</param>
|
/// <param name="status">The status string.</param>
|
||||||
/// <param name="enumValue">The <see cref="SeriesStatus"/>.</param>
|
/// <param name="enumValue">The <see cref="SeriesStatus"/>.</param>
|
||||||
/// <returns>Returns true if parsing was successful.</returns>
|
/// <returns>Returns true if parsing was successful.</returns>
|
||||||
public static bool TryParseSeriesStatus(string? status, out SeriesStatus? enumValue)
|
public static bool TryParseSeriesStatus(string status, out SeriesStatus? enumValue)
|
||||||
{
|
{
|
||||||
if (Enum.TryParse(status, true, out SeriesStatus seriesStatus))
|
if (Enum.TryParse(status, true, out SeriesStatus seriesStatus))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Emby.Naming.Video
|
|||||||
var match = expression.Match(name);
|
var match = expression.Match(name);
|
||||||
if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned))
|
if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned))
|
||||||
{
|
{
|
||||||
newName = cleaned.Value.Trim();
|
newName = cleaned.Value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -217,8 +217,6 @@ namespace Emby.Naming.Video
|
|||||||
// The CleanStringParser should have removed common keywords etc.
|
// The CleanStringParser should have removed common keywords etc.
|
||||||
return testFilename.IsEmpty
|
return testFilename.IsEmpty
|
||||||
|| testFilename[0] == '-'
|
|| testFilename[0] == '-'
|
||||||
|| testFilename[0] == '_'
|
|
||||||
|| testFilename[0] == '.'
|
|
||||||
|| CheckMultiVersionRegex().IsMatch(testFilename);
|
|| CheckMultiVersionRegex().IsMatch(testFilename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1019,15 +1019,6 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
{
|
{
|
||||||
dto.AlbumId = albumParent.Id;
|
dto.AlbumId = albumParent.Id;
|
||||||
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
|
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
|
||||||
if (albumParent.LUFS.HasValue)
|
|
||||||
{
|
|
||||||
// -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
|
|
||||||
dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
|
|
||||||
}
|
|
||||||
else if (albumParent.NormalizationGain.HasValue)
|
|
||||||
{
|
|
||||||
dto.AlbumNormalizationGain = albumParent.NormalizationGain;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (options.ContainsField(ItemFields.MediaSourceCount))
|
// if (options.ContainsField(ItemFields.MediaSourceCount))
|
||||||
|
|||||||
@@ -267,11 +267,8 @@ namespace Emby.Server.Implementations.Images
|
|||||||
{
|
{
|
||||||
var image = item.GetImageInfo(type, 0);
|
var image = item.GetImageInfo(type, 0);
|
||||||
|
|
||||||
if (image is null)
|
if (image is not null)
|
||||||
{
|
{
|
||||||
return GetItemsWithImages(item).Count is not 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!image.IsLocalFile)
|
if (!image.IsLocalFile)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -286,6 +283,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Images
|
|||||||
includeItemTypes = new[] { BaseItemKind.Series };
|
includeItemTypes = new[] { BaseItemKind.Series };
|
||||||
break;
|
break;
|
||||||
case CollectionType.music:
|
case CollectionType.music:
|
||||||
includeItemTypes = new[] { BaseItemKind.MusicArtist }; // Music albums usually don't have dedicated backdrops, so use artist instead
|
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
|
||||||
break;
|
break;
|
||||||
case CollectionType.musicvideos:
|
case CollectionType.musicvideos:
|
||||||
includeItemTypes = new[] { BaseItemKind.MusicVideo };
|
includeItemTypes = new[] { BaseItemKind.MusicVideo };
|
||||||
|
|||||||
@@ -31,20 +31,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
"**/*.sample.?????",
|
"**/*.sample.?????",
|
||||||
"**/sample/*",
|
"**/sample/*",
|
||||||
|
|
||||||
// Avoid adding Hungarian sample files
|
|
||||||
// https://github.com/jellyfin/jellyfin/issues/16237
|
|
||||||
"**/minta.?",
|
|
||||||
"**/minta.??",
|
|
||||||
"**/minta.???", // Matches minta.mkv
|
|
||||||
"**/minta.????", // Matches minta.webm
|
|
||||||
"**/minta.?????",
|
|
||||||
"**/*.minta.?",
|
|
||||||
"**/*.minta.??",
|
|
||||||
"**/*.minta.???",
|
|
||||||
"**/*.minta.????",
|
|
||||||
"**/*.minta.?????",
|
|
||||||
"**/minta/*",
|
|
||||||
|
|
||||||
// Directories
|
// Directories
|
||||||
"**/metadata/**",
|
"**/metadata/**",
|
||||||
"**/metadata",
|
"**/metadata",
|
||||||
|
|||||||
@@ -2289,7 +2289,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return [];
|
return new List<Folder>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return GetCollectionFoldersInternal(item, allUserRootChildren);
|
return GetCollectionFoldersInternal(item, allUserRootChildren);
|
||||||
|
|||||||
@@ -37,27 +37,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
while (attributeIndex > -1 && attributeIndex < maxIndex)
|
while (attributeIndex > -1 && attributeIndex < maxIndex)
|
||||||
{
|
{
|
||||||
var attributeEnd = attributeIndex + attribute.Length;
|
var attributeEnd = attributeIndex + attribute.Length;
|
||||||
if (attributeIndex > 0)
|
if (attributeIndex > 0
|
||||||
|
&& str[attributeIndex - 1] == '['
|
||||||
|
&& (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
|
||||||
{
|
{
|
||||||
var attributeOpener = str[attributeIndex - 1];
|
var closingIndex = str[attributeEnd..].IndexOf(']');
|
||||||
var attributeCloser = attributeOpener switch
|
|
||||||
{
|
|
||||||
'[' => ']',
|
|
||||||
'(' => ')',
|
|
||||||
'{' => '}',
|
|
||||||
_ => '\0'
|
|
||||||
};
|
|
||||||
if (attributeCloser != '\0' && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
|
|
||||||
{
|
|
||||||
var closingIndex = str[attributeEnd..].IndexOf(attributeCloser);
|
|
||||||
|
|
||||||
// Must be at least 1 character before the closing bracket.
|
// Must be at least 1 character before the closing bracket.
|
||||||
if (closingIndex > 1)
|
if (closingIndex > 1)
|
||||||
{
|
{
|
||||||
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
|
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
str = str[attributeEnd..];
|
str = str[attributeEnd..];
|
||||||
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
|||||||
{
|
{
|
||||||
public class BookResolver : ItemResolver<Book>
|
public class BookResolver : ItemResolver<Book>
|
||||||
{
|
{
|
||||||
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf", ".m4b", ".m4a", ".aac", ".flac", ".mp3", ".opus" };
|
private readonly string[] _validExtensions = { ".azw", ".azw3", ".cb7", ".cbr", ".cbt", ".cbz", ".epub", ".mobi", ".pdf" };
|
||||||
|
|
||||||
protected override Book Resolve(ItemResolveArgs args)
|
protected override Book Resolve(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
{
|
{
|
||||||
"Albums": "аальбомқәа",
|
"Albums": "аальбомқәа"
|
||||||
"AppDeviceValues": "Апп: {0}, Априбор: {1}",
|
|
||||||
"Application": "Апрограмма"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,7 +135,5 @@
|
|||||||
"TaskExtractMediaSegments": "Media Segment Skandeer",
|
"TaskExtractMediaSegments": "Media Segment Skandeer",
|
||||||
"TaskExtractMediaSegmentsDescription": "Onttrek of verkry mediasegmente van MediaSegment-geaktiveerde inproppe.",
|
"TaskExtractMediaSegmentsDescription": "Onttrek of verkry mediasegmente van MediaSegment-geaktiveerde inproppe.",
|
||||||
"TaskMoveTrickplayImages": "Migreer Trickplay Beeldligging",
|
"TaskMoveTrickplayImages": "Migreer Trickplay Beeldligging",
|
||||||
"TaskMoveTrickplayImagesDescription": "Skuif ontstaande trickplay lêers volgens die biblioteekinstellings.",
|
"TaskMoveTrickplayImagesDescription": "Skuif ontstaande trickplay lêers volgens die biblioteekinstellings."
|
||||||
"CleanupUserDataTask": "Gebruikers data skoon maak taak",
|
|
||||||
"CleanupUserDataTaskDescription": "Maak alle gebruikers data (kykstatus, gunstelingstatus, ens.) skoon van media wat nie meer vir ten minste 90 dae teenwoordig is nie."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"Playlists": "Плэй-лісты",
|
"Playlists": "Плэй-лісты",
|
||||||
"Latest": "Апошняе",
|
"Latest": "Апошняе",
|
||||||
"LabelIpAddressValue": "IP-адрас: {0}",
|
"LabelIpAddressValue": "IP-адрас: {0}",
|
||||||
"ItemAddedWithName": "{0} дададзены ў бібліятэку",
|
"ItemAddedWithName": "{0} даданы ў бібліятэку",
|
||||||
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
|
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана",
|
"NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана",
|
||||||
"PluginInstalledWithName": "{0} быў усталяваны",
|
"PluginInstalledWithName": "{0} быў усталяваны",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"Channels": "Каналы",
|
"Channels": "Каналы",
|
||||||
"ChapterNameValue": "Раздзел {0}",
|
"ChapterNameValue": "Раздзел {0}",
|
||||||
"Collections": "Калекцыі",
|
"Collections": "Калекцыі",
|
||||||
"Default": "Прадвызначана",
|
"Default": "Па змаўчанні",
|
||||||
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
|
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
|
||||||
"Folders": "Папкі",
|
"Folders": "Папкі",
|
||||||
"Favorites": "Абранае",
|
"Favorites": "Абранае",
|
||||||
@@ -81,8 +81,8 @@
|
|||||||
"NotificationOptionInstallationFailed": "Збой усталёўкі",
|
"NotificationOptionInstallationFailed": "Збой усталёўкі",
|
||||||
"NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.",
|
"NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.",
|
||||||
"NotificationOptionCameraImageUploaded": "Выява камеры запампавана",
|
"NotificationOptionCameraImageUploaded": "Выява камеры запампавана",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыя спынена",
|
"NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыё спынена",
|
||||||
"NotificationOptionAudioPlayback": "Прайграванне аўдыя пачалося",
|
"NotificationOptionAudioPlayback": "Прайграванне аўдыё пачалося",
|
||||||
"NotificationOptionNewLibraryContent": "Дададзены новы кантэнт",
|
"NotificationOptionNewLibraryContent": "Дададзены новы кантэнт",
|
||||||
"NotificationOptionPluginError": "Збой плагіна",
|
"NotificationOptionPluginError": "Збой плагіна",
|
||||||
"NotificationOptionPluginUninstalled": "Плагін выдалены",
|
"NotificationOptionPluginUninstalled": "Плагін выдалены",
|
||||||
@@ -123,10 +123,10 @@
|
|||||||
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
||||||
"TaskRefreshChannels": "Абнавіць каналы",
|
"TaskRefreshChannels": "Абнавіць каналы",
|
||||||
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
|
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
|
||||||
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа выконвацца доўга.",
|
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.",
|
||||||
"TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
|
"TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
|
||||||
"TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
|
"TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
|
||||||
"TaskCleanCollectionsAndPlaylists": "Ачысціць калекцыі і плэй-лісты",
|
"TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
|
"TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
|
||||||
"TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
|
"TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
|
||||||
"TaskAudioNormalization": "Нармалізацыя гуку",
|
"TaskAudioNormalization": "Нармалізацыя гуку",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"Favorites": "Любими",
|
"Favorites": "Любими",
|
||||||
"Folders": "Папки",
|
"Folders": "Папки",
|
||||||
"Genres": "Жанрове",
|
"Genres": "Жанрове",
|
||||||
"HeaderAlbumArtists": "Изпълнители на албума",
|
"HeaderAlbumArtists": "Изпълнители на албуми",
|
||||||
"HeaderContinueWatching": "Продължаване на гледането",
|
"HeaderContinueWatching": "Продължаване на гледането",
|
||||||
"HeaderFavoriteAlbums": "Любими албуми",
|
"HeaderFavoriteAlbums": "Любими албуми",
|
||||||
"HeaderFavoriteArtists": "Любими изпълнители",
|
"HeaderFavoriteArtists": "Любими изпълнители",
|
||||||
|
|||||||
@@ -63,8 +63,8 @@
|
|||||||
"Photos": "Fotos",
|
"Photos": "Fotos",
|
||||||
"Playlists": "Llistes de reproducció",
|
"Playlists": "Llistes de reproducció",
|
||||||
"Plugin": "Complement",
|
"Plugin": "Complement",
|
||||||
"PluginInstalledWithName": "{0} s'ha instal·lat",
|
"PluginInstalledWithName": "{0} ha estat instal·lat",
|
||||||
"PluginUninstalledWithName": "{0} s'ha desinstal·lat",
|
"PluginUninstalledWithName": "S'ha instal·lat {0}",
|
||||||
"PluginUpdatedWithName": "S'ha actualitzat {0}",
|
"PluginUpdatedWithName": "S'ha actualitzat {0}",
|
||||||
"ProviderValue": "Proveïdor: {0}",
|
"ProviderValue": "Proveïdor: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} ha fallat",
|
"ScheduledTaskFailedWithName": "{0} ha fallat",
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
"TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.",
|
"TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.",
|
||||||
"TaskCleanLogs": "Neteja dels registres",
|
"TaskCleanLogs": "Neteja dels registres",
|
||||||
"TaskRefreshLibraryDescription": "Escaneja les mediateques, a la cerca de fitxers nous i refresca les metadades.",
|
"TaskRefreshLibraryDescription": "Escaneja les mediateques, a la cerca de fitxers nous i refresca les metadades.",
|
||||||
"TaskRefreshLibrary": "Escaneja la mediateca",
|
"TaskRefreshLibrary": "Escaneig de les mediateques",
|
||||||
"TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.",
|
"TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.",
|
||||||
"TaskRefreshChapterImages": "Extracció de les imatges dels capítols",
|
"TaskRefreshChapterImages": "Extracció de les imatges dels capítols",
|
||||||
"TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.",
|
"TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
"HeaderContinueWatching": "Weiterschauen",
|
"HeaderContinueWatching": "Weiterschauen",
|
||||||
"HeaderFavoriteAlbums": "Lieblingsalben",
|
"HeaderFavoriteAlbums": "Lieblingsalben",
|
||||||
"HeaderFavoriteArtists": "Lieblingsinterpreten",
|
"HeaderFavoriteArtists": "Lieblingsinterpreten",
|
||||||
"HeaderFavoriteEpisodes": "Lieblingsfolgen",
|
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
|
||||||
"HeaderFavoriteShows": "Lieblingsserien",
|
"HeaderFavoriteShows": "Lieblingsserien",
|
||||||
"HeaderFavoriteSongs": "Lieblingssongs",
|
"HeaderFavoriteSongs": "Lieblingssongs",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "Live TV",
|
||||||
|
|||||||
@@ -133,8 +133,8 @@
|
|||||||
"TaskDownloadMissingLyrics": "Hangi puuduvad laulusõnad",
|
"TaskDownloadMissingLyrics": "Hangi puuduvad laulusõnad",
|
||||||
"TaskDownloadMissingLyricsDescription": "Laulusõnade allalaadimine",
|
"TaskDownloadMissingLyricsDescription": "Laulusõnade allalaadimine",
|
||||||
"TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.",
|
"TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.",
|
||||||
"TaskExtractMediaSegments": "Skaneeri meedialõike",
|
"TaskExtractMediaSegments": "Skaneeri meediasegmente",
|
||||||
"TaskExtractMediaSegmentsDescription": "Eraldab või võtab meedialõigud MediaSegment'i toega 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, mida pole enam vähemalt 90 päeva saadaval olnud."
|
||||||
|
|||||||
@@ -14,9 +14,5 @@
|
|||||||
"DeviceOnlineWithName": "{0} er sambundið",
|
"DeviceOnlineWithName": "{0} er sambundið",
|
||||||
"Favorites": "Yndis",
|
"Favorites": "Yndis",
|
||||||
"Folders": "Mappur",
|
"Folders": "Mappur",
|
||||||
"Forced": "Kravt",
|
"Forced": "Kravt"
|
||||||
"FailedLoginAttemptWithUserName": "Miseydnað innritanarroynd frá {0}",
|
|
||||||
"HeaderFavoriteEpisodes": "Yndispartar",
|
|
||||||
"HeaderFavoriteSongs": "Yndissangir",
|
|
||||||
"LabelIpAddressValue": "IP atsetur: {0}"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1 @@
|
|||||||
{
|
{}
|
||||||
"Books": "ספרים",
|
|
||||||
"NameSeasonNumber": "עונה {0}",
|
|
||||||
"Channels": "ערוצים",
|
|
||||||
"Movies": "סרטים",
|
|
||||||
"Music": "מוזיקה",
|
|
||||||
"Collections": "אוספים",
|
|
||||||
"Albums": "אלבומים",
|
|
||||||
"Application": "אפליקציה",
|
|
||||||
"Artists": "אמנים",
|
|
||||||
"ChapterNameValue": "פרק {0}",
|
|
||||||
"External": "חיצונית",
|
|
||||||
"Favorites": "מועדפים",
|
|
||||||
"Folders": "תיקיות",
|
|
||||||
"Genres": "ז'אנרים",
|
|
||||||
"HeaderAlbumArtists": "אמני אלבומים",
|
|
||||||
"HeaderContinueWatching": "להמשיך לצפות",
|
|
||||||
"HeaderFavoriteAlbums": "אלבומים אהובים",
|
|
||||||
"HeaderFavoriteArtists": "אמנים אהובים",
|
|
||||||
"HeaderFavoriteEpisodes": "פרקים אהובים",
|
|
||||||
"HeaderFavoriteShows": "תוכניות אהובות",
|
|
||||||
"HeaderFavoriteSongs": "שירים אהובים",
|
|
||||||
"HeaderLiveTV": "טלוויזיה בשידור חי",
|
|
||||||
"HeaderNextUp": "הבא",
|
|
||||||
"HearingImpaired": "ללקויי שמיעה",
|
|
||||||
"HomeVideos": "סרטונים ביתיים"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
"TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे",
|
"TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे",
|
||||||
"TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे.",
|
"TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे.",
|
||||||
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
|
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
|
||||||
"TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें।",
|
"TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें",
|
||||||
"TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ",
|
"TaskDownloadMissingLyrics": "लापता गानों के बोल डाउनलोड करेँ",
|
||||||
"TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है",
|
"TaskDownloadMissingLyricsDescription": "गानों के बोल डाउनलोड करता है",
|
||||||
"TaskExtractMediaSegments": "मीडिया सेगमेंट स्कैन",
|
"TaskExtractMediaSegments": "मीडिया सेगमेंट स्कैन",
|
||||||
@@ -136,5 +136,5 @@
|
|||||||
"TaskMoveTrickplayImagesDescription": "लाइब्रेरी सेटिंग्स के अनुसार मौजूदा ट्रिकप्ले फ़ाइलों को स्थानांतरित करता है।",
|
"TaskMoveTrickplayImagesDescription": "लाइब्रेरी सेटिंग्स के अनुसार मौजूदा ट्रिकप्ले फ़ाइलों को स्थानांतरित करता है।",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "संग्रहों और प्लेलिस्टों से उन आइटमों को हटाता है जो अब मौजूद नहीं हैं।",
|
"TaskCleanCollectionsAndPlaylistsDescription": "संग्रहों और प्लेलिस्टों से उन आइटमों को हटाता है जो अब मौजूद नहीं हैं।",
|
||||||
"TaskCleanCollectionsAndPlaylists": "संग्रह और प्लेलिस्ट साफ़ करें",
|
"TaskCleanCollectionsAndPlaylists": "संग्रह और प्लेलिस्ट साफ़ करें",
|
||||||
"CleanupUserDataTask": "यूज़र डेटा सफाई कार्य"
|
"CleanupUserDataTask": "यूज़र डेटा की सफाई करता है।"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"CameraImageUploadedFrom": "Nova fotografija sa kamere je učitana iz {0}",
|
"CameraImageUploadedFrom": "Nova fotografija sa kamere je učitana iz {0}",
|
||||||
"Channels": "Kanali",
|
"Channels": "Kanali",
|
||||||
"ChapterNameValue": "Poglavlje {0}",
|
"ChapterNameValue": "Poglavlje {0}",
|
||||||
"Collections": "Zbirke",
|
"Collections": "Kolekcije",
|
||||||
"DeviceOfflineWithName": "{0} je prekinuo vezu",
|
"DeviceOfflineWithName": "{0} je prekinuo vezu",
|
||||||
"DeviceOnlineWithName": "{0} je povezan",
|
"DeviceOnlineWithName": "{0} je povezan",
|
||||||
"FailedLoginAttemptWithUserName": "Neuspješan pokušaj prijave od {0}",
|
"FailedLoginAttemptWithUserName": "Neuspješan pokušaj prijave od {0}",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"ScheduledTaskFailedWithName": "{0} neuspjelo",
|
"ScheduledTaskFailedWithName": "{0} neuspjelo",
|
||||||
"ScheduledTaskStartedWithName": "{0} pokrenuto",
|
"ScheduledTaskStartedWithName": "{0} pokrenuto",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} treba ponovno pokrenuti",
|
"ServerNameNeedsToBeRestarted": "{0} treba ponovno pokrenuti",
|
||||||
"Shows": "Emisije",
|
"Shows": "Serije",
|
||||||
"Songs": "Pjesme",
|
"Songs": "Pjesme",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin server se učitava. Pokušajte ponovo uskoro.",
|
"StartupEmbyServerIsLoading": "Jellyfin server se učitava. Pokušajte ponovo uskoro.",
|
||||||
"SubtitleDownloadFailureFromForItem": "Prijevod nije uspješno preuzet od {0} za {1}",
|
"SubtitleDownloadFailureFromForItem": "Prijevod nije uspješno preuzet od {0} za {1}",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
|
"AppDeviceValues": "App: {0}, Dispositivo: {1}",
|
||||||
"Application": "Applicazione",
|
"Application": "Applicazione",
|
||||||
"Artists": "Artisti",
|
"Artists": "Artisti",
|
||||||
"AuthenticationSucceededWithUserName": "{0} autenticato correttamente",
|
"AuthenticationSucceededWithUserName": "{0} autenticato con successo",
|
||||||
"Books": "Libri",
|
"Books": "Libri",
|
||||||
"CameraImageUploadedFrom": "È stata caricata una nuova fotografia da {0}",
|
"CameraImageUploadedFrom": "È stata caricata una nuova fotografia da {0}",
|
||||||
"Channels": "Canali",
|
"Channels": "Canali",
|
||||||
@@ -11,36 +11,36 @@
|
|||||||
"Collections": "Collezioni",
|
"Collections": "Collezioni",
|
||||||
"DeviceOfflineWithName": "{0} si è disconnesso",
|
"DeviceOfflineWithName": "{0} si è disconnesso",
|
||||||
"DeviceOnlineWithName": "{0} è connesso",
|
"DeviceOnlineWithName": "{0} è connesso",
|
||||||
"FailedLoginAttemptWithUserName": "Tentativo di accesso non riuscito da {0}",
|
"FailedLoginAttemptWithUserName": "Tentativo di accesso fallito da {0}",
|
||||||
"Favorites": "Preferiti",
|
"Favorites": "Preferiti",
|
||||||
"Folders": "Cartelle",
|
"Folders": "Cartelle",
|
||||||
"Genres": "Generi",
|
"Genres": "Generi",
|
||||||
"HeaderAlbumArtists": "Artisti dell'album",
|
"HeaderAlbumArtists": "Artisti dell'album",
|
||||||
"HeaderContinueWatching": "Continua a guardare",
|
"HeaderContinueWatching": "Continua a guardare",
|
||||||
"HeaderFavoriteAlbums": "Album preferiti",
|
"HeaderFavoriteAlbums": "Album Preferiti",
|
||||||
"HeaderFavoriteArtists": "Artisti preferiti",
|
"HeaderFavoriteArtists": "Artisti Preferiti",
|
||||||
"HeaderFavoriteEpisodes": "Episodi preferiti",
|
"HeaderFavoriteEpisodes": "Episodi Preferiti",
|
||||||
"HeaderFavoriteShows": "Serie TV preferite",
|
"HeaderFavoriteShows": "Serie TV Preferite",
|
||||||
"HeaderFavoriteSongs": "Brani preferiti",
|
"HeaderFavoriteSongs": "Brani Preferiti",
|
||||||
"HeaderLiveTV": "Diretta TV",
|
"HeaderLiveTV": "Diretta TV",
|
||||||
"HeaderNextUp": "Prossimo",
|
"HeaderNextUp": "Prossimo",
|
||||||
"HeaderRecordingGroups": "Gruppi di registrazione",
|
"HeaderRecordingGroups": "Gruppi di Registrazione",
|
||||||
"HomeVideos": "Video personali",
|
"HomeVideos": "Video Personali",
|
||||||
"Inherit": "Eredita",
|
"Inherit": "Eredita",
|
||||||
"ItemAddedWithName": "{0} è stato aggiunto alla libreria",
|
"ItemAddedWithName": "{0} è stato aggiunto alla libreria",
|
||||||
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
"ItemRemovedWithName": "{0} è stato rimosso dalla libreria",
|
||||||
"LabelIpAddressValue": "Indirizzo IP: {0}",
|
"LabelIpAddressValue": "Indirizzo IP: {0}",
|
||||||
"LabelRunningTimeValue": "Durata: {0}",
|
"LabelRunningTimeValue": "Durata: {0}",
|
||||||
"Latest": "Novità",
|
"Latest": "Novità",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server è stato aggiornato",
|
"MessageApplicationUpdated": "Il Server Jellyfin è stato aggiornato",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server è stato aggiornato a {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
"MessageNamedServerConfigurationUpdatedWithValue": "La sezione {0} della configurazione server è stata aggiornata",
|
||||||
"MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata",
|
"MessageServerConfigurationUpdated": "La configurazione del server è stata aggiornata",
|
||||||
"MixedContent": "Contenuto misto",
|
"MixedContent": "Contenuto misto",
|
||||||
"Movies": "Film",
|
"Movies": "Film",
|
||||||
"Music": "Musica",
|
"Music": "Musica",
|
||||||
"MusicVideos": "Video musicali",
|
"MusicVideos": "Video Musicali",
|
||||||
"NameInstallFailed": "{0} installazione non riuscita",
|
"NameInstallFailed": "{0} installazione fallita",
|
||||||
"NameSeasonNumber": "Stagione {0}",
|
"NameSeasonNumber": "Stagione {0}",
|
||||||
"NameSeasonUnknown": "Stagione sconosciuta",
|
"NameSeasonUnknown": "Stagione sconosciuta",
|
||||||
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
"NewVersionIsAvailable": "Una nuova versione di Jellyfin Server è disponibile per il download.",
|
||||||
@@ -49,37 +49,37 @@
|
|||||||
"NotificationOptionAudioPlayback": "La riproduzione audio è iniziata",
|
"NotificationOptionAudioPlayback": "La riproduzione audio è iniziata",
|
||||||
"NotificationOptionAudioPlaybackStopped": "La riproduzione audio è stata interrotta",
|
"NotificationOptionAudioPlaybackStopped": "La riproduzione audio è stata interrotta",
|
||||||
"NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata",
|
"NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata",
|
||||||
"NotificationOptionInstallationFailed": "Installazione non riuscita",
|
"NotificationOptionInstallationFailed": "Installazione fallita",
|
||||||
"NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto",
|
"NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto",
|
||||||
"NotificationOptionPluginError": "Errore del plugin",
|
"NotificationOptionPluginError": "Errore del plugin",
|
||||||
"NotificationOptionPluginInstalled": "Plugin installato",
|
"NotificationOptionPluginInstalled": "Plugin installato",
|
||||||
"NotificationOptionPluginUninstalled": "Plugin disinstallato",
|
"NotificationOptionPluginUninstalled": "Plugin disinstallato",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Aggiornamento plugin installato",
|
"NotificationOptionPluginUpdateInstalled": "Aggiornamento plugin installato",
|
||||||
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
|
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
|
||||||
"NotificationOptionTaskFailed": "Operazione pianificata non riuscita",
|
"NotificationOptionTaskFailed": "Operazione pianificata fallita",
|
||||||
"NotificationOptionUserLockedOut": "Utente bloccato",
|
"NotificationOptionUserLockedOut": "Utente bloccato",
|
||||||
"NotificationOptionVideoPlayback": "Riproduzione video iniziata",
|
"NotificationOptionVideoPlayback": "Riproduzione video iniziata",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta",
|
"NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta",
|
||||||
"Photos": "Foto",
|
"Photos": "Foto",
|
||||||
"Playlists": "Scalette",
|
"Playlists": "Playlist",
|
||||||
"Plugin": "Plugin",
|
"Plugin": "Plugin",
|
||||||
"PluginInstalledWithName": "{0} è stato installato",
|
"PluginInstalledWithName": "{0} è stato Installato",
|
||||||
"PluginUninstalledWithName": "{0} è stato disinstallato",
|
"PluginUninstalledWithName": "{0} è stato disinstallato",
|
||||||
"PluginUpdatedWithName": "{0} è stato aggiornato",
|
"PluginUpdatedWithName": "{0} è stato aggiornato",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Provider: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} non riuscito",
|
"ScheduledTaskFailedWithName": "{0} fallito",
|
||||||
"ScheduledTaskStartedWithName": "{0} avviato",
|
"ScheduledTaskStartedWithName": "{0} avviato",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
|
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
|
||||||
"Shows": "Serie TV",
|
"Shows": "Serie TV",
|
||||||
"Songs": "Brani",
|
"Songs": "Brani",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Server si sta avviando. Riprova più tardi.",
|
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
|
||||||
"SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}",
|
"SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}",
|
||||||
"Sync": "Sincronizza",
|
"Sync": "Sincronizza",
|
||||||
"System": "Sistema",
|
"System": "Sistema",
|
||||||
"TvShows": "Serie TV",
|
"TvShows": "Serie TV",
|
||||||
"User": "Utente",
|
"User": "Utente",
|
||||||
"UserCreatedWithName": "L'utente {0} è stato creato",
|
"UserCreatedWithName": "L'utente {0} è stato creato",
|
||||||
"UserDeletedWithName": "L'utente {0} è stato eliminato",
|
"UserDeletedWithName": "L'utente {0} è stato rimosso",
|
||||||
"UserDownloadingItemWithValues": "{0} sta scaricando {1}",
|
"UserDownloadingItemWithValues": "{0} sta scaricando {1}",
|
||||||
"UserLockedOutWithName": "L'utente {0} è stato bloccato",
|
"UserLockedOutWithName": "L'utente {0} è stato bloccato",
|
||||||
"UserOfflineFromDevice": "{0} si è disconnesso da {1}",
|
"UserOfflineFromDevice": "{0} si è disconnesso da {1}",
|
||||||
@@ -114,20 +114,20 @@
|
|||||||
"TasksLibraryCategory": "Libreria",
|
"TasksLibraryCategory": "Libreria",
|
||||||
"TasksMaintenanceCategory": "Manutenzione",
|
"TasksMaintenanceCategory": "Manutenzione",
|
||||||
"TaskCleanActivityLog": "Attività di Registro Completate",
|
"TaskCleanActivityLog": "Attività di Registro Completate",
|
||||||
"TaskCleanActivityLogDescription": "Elimina le voci del registro delle attività più vecchie dell'età configurata.",
|
"TaskCleanActivityLogDescription": "Elimina le voci del registro delle attività più vecchie dell’età configurata.",
|
||||||
"Undefined": "Non specificato",
|
"Undefined": "Non Definito",
|
||||||
"Forced": "Forzato",
|
"Forced": "Forzato",
|
||||||
"Default": "Predefinito",
|
"Default": "Predefinito",
|
||||||
"TaskOptimizeDatabaseDescription": "Compatta database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altre modifiche inerenti il database potrebbe aumentarne le prestazioni.",
|
"TaskOptimizeDatabaseDescription": "Compatta database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altre modifiche inerenti il database potrebbe aumentarne le prestazioni.",
|
||||||
"TaskOptimizeDatabase": "Ottimizza database",
|
"TaskOptimizeDatabase": "Ottimizza database",
|
||||||
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
||||||
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori scalette HLS. Questa procedura potrebbe richiedere molto tempo.",
|
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
|
||||||
"External": "Esterno",
|
"External": "Esterno",
|
||||||
"HearingImpaired": "Non udenti",
|
"HearingImpaired": "Non Udenti",
|
||||||
"TaskRefreshTrickplayImages": "Genera immagini Trickplay",
|
"TaskRefreshTrickplayImages": "Genera immagini Trickplay",
|
||||||
"TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.",
|
"TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.",
|
||||||
"TaskCleanCollectionsAndPlaylists": "Ripulisci le collezioni e le scalette",
|
"TaskCleanCollectionsAndPlaylists": "Ripulire le collezioni e le playlist",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle collezioni e dalle scalette che non esistono più.",
|
"TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle collezioni e dalle playlist che non esistono più.",
|
||||||
"TaskAudioNormalization": "Normalizzazione dell'audio",
|
"TaskAudioNormalization": "Normalizzazione dell'audio",
|
||||||
"TaskAudioNormalizationDescription": "Scansiona i file alla ricerca dei dati per la normalizzazione dell'audio.",
|
"TaskAudioNormalizationDescription": "Scansiona i file alla ricerca dei dati per la normalizzazione dell'audio.",
|
||||||
"TaskDownloadMissingLyricsDescription": "Scarica testi per le canzoni",
|
"TaskDownloadMissingLyricsDescription": "Scarica testi per le canzoni",
|
||||||
|
|||||||
@@ -43,32 +43,32 @@
|
|||||||
"NameInstallFailed": "{0}のインストールに失敗しました",
|
"NameInstallFailed": "{0}のインストールに失敗しました",
|
||||||
"NameSeasonNumber": "シーズン {0}",
|
"NameSeasonNumber": "シーズン {0}",
|
||||||
"NameSeasonUnknown": "シーズン不明",
|
"NameSeasonUnknown": "シーズン不明",
|
||||||
"NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロードできます。",
|
"NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
|
"NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
|
"NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
|
||||||
"NotificationOptionAudioPlayback": "オーディオの再生を開始",
|
"NotificationOptionAudioPlayback": "オーディオの再生を開始",
|
||||||
"NotificationOptionAudioPlaybackStopped": "オーディオの再生を停止",
|
"NotificationOptionAudioPlaybackStopped": "オーディオの再生をストップしました",
|
||||||
"NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました",
|
"NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました",
|
||||||
"NotificationOptionInstallationFailed": "インストール失敗",
|
"NotificationOptionInstallationFailed": "インストール失敗",
|
||||||
"NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました",
|
"NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました",
|
||||||
"NotificationOptionPluginError": "プラグインに障害が発生しました",
|
"NotificationOptionPluginError": "プラグインに障害が発生しました",
|
||||||
"NotificationOptionPluginInstalled": "プラグインをインストールしました",
|
"NotificationOptionPluginInstalled": "プラグインがインストールされました",
|
||||||
"NotificationOptionPluginUninstalled": "プラグインをアンインストールしました",
|
"NotificationOptionPluginUninstalled": "プラグインがアンインストールされました",
|
||||||
"NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました",
|
"NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました",
|
||||||
"NotificationOptionServerRestartRequired": "サーバーを再起動してください",
|
"NotificationOptionServerRestartRequired": "サーバーを再起動してください",
|
||||||
"NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗",
|
"NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗",
|
||||||
"NotificationOptionUserLockedOut": "ユーザーはロックされています",
|
"NotificationOptionUserLockedOut": "ユーザーはロックされています",
|
||||||
"NotificationOptionVideoPlayback": "ビデオの再生を開始",
|
"NotificationOptionVideoPlayback": "ビデオの再生を開始しました",
|
||||||
"NotificationOptionVideoPlaybackStopped": "ビデオの再生を停止",
|
"NotificationOptionVideoPlaybackStopped": "ビデオを停止しました",
|
||||||
"Photos": "フォト",
|
"Photos": "フォト",
|
||||||
"Playlists": "プレイリスト",
|
"Playlists": "プレイリスト",
|
||||||
"Plugin": "プラグイン",
|
"Plugin": "プラグイン",
|
||||||
"PluginInstalledWithName": "{0} をインストールしました",
|
"PluginInstalledWithName": "{0} がインストールされました",
|
||||||
"PluginUninstalledWithName": "{0} をアンインストールしました",
|
"PluginUninstalledWithName": "{0} がアンインストールされました",
|
||||||
"PluginUpdatedWithName": "{0} を更新しました",
|
"PluginUpdatedWithName": "{0} が更新されました",
|
||||||
"ProviderValue": "プロバイダ: {0}",
|
"ProviderValue": "プロバイダ: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} が失敗しました",
|
"ScheduledTaskFailedWithName": "{0} が失敗しました",
|
||||||
"ScheduledTaskStartedWithName": "{0} を開始",
|
"ScheduledTaskStartedWithName": "{0} が開始されました",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} を再起動してください",
|
"ServerNameNeedsToBeRestarted": "{0} を再起動してください",
|
||||||
"Shows": "番組",
|
"Shows": "番組",
|
||||||
"Songs": "曲",
|
"Songs": "曲",
|
||||||
|
|||||||
@@ -9,46 +9,46 @@
|
|||||||
"Artists": "არტისტი",
|
"Artists": "არტისტი",
|
||||||
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
|
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
|
||||||
"Books": "წიგნები",
|
"Books": "წიგნები",
|
||||||
"Forced": "იძულებითი",
|
"Forced": "ძალით",
|
||||||
"Inherit": "მემკვიდრეობით",
|
"Inherit": "მემკვიდრეობით",
|
||||||
"Latest": "უახლესი",
|
"Latest": "უახლესი",
|
||||||
"Movies": "ფილმები",
|
"Movies": "ფილმები",
|
||||||
"Music": "მუსიკა",
|
"Music": "მუსიკა",
|
||||||
"Photos": "ფოტოები",
|
"Photos": "ფოტოები",
|
||||||
"Playlists": "დასაკრავი სიები",
|
"Playlists": "დასაკრავი სიები",
|
||||||
"Plugin": "მოდული",
|
"Plugin": "დამატება",
|
||||||
"Shows": "სერიალები",
|
"Shows": "სერიალები",
|
||||||
"Songs": "სიმღერები",
|
"Songs": "სიმღერები",
|
||||||
"Sync": "სინქრონიზაცია",
|
"Sync": "სინქრონიზაცია",
|
||||||
"System": "სისტემა",
|
"System": "სისტემა",
|
||||||
"Undefined": "განუსაზღვრელი",
|
"Undefined": "აღუწერელი",
|
||||||
"User": "მომხმარებელი",
|
"User": "მომხმარებელი",
|
||||||
"TasksMaintenanceCategory": "რემონტი",
|
"TasksMaintenanceCategory": "რემონტი",
|
||||||
"TasksLibraryCategory": "ბიბლიოთეკა",
|
"TasksLibraryCategory": "ბიბლიოთეკა",
|
||||||
"ChapterNameValue": "თავი {0}",
|
"ChapterNameValue": "თავი {0}",
|
||||||
"HeaderContinueWatching": "ყურების გაგრძელება",
|
"HeaderContinueWatching": "ყურების გაგრძელება",
|
||||||
"HeaderFavoriteArtists": "რჩეული შემსრულებლები",
|
"HeaderFavoriteArtists": "რჩეული შემსრულებლები",
|
||||||
"DeviceOfflineWithName": "{0} გამოეთიშა",
|
"DeviceOfflineWithName": "{0} გაითიშა",
|
||||||
"External": "გარე",
|
"External": "გარე",
|
||||||
"HeaderFavoriteEpisodes": "რჩეული ეპიზოდები",
|
"HeaderFavoriteEpisodes": "რჩეული ეპიზოდები",
|
||||||
"HeaderFavoriteSongs": "რჩეული სიმღერები",
|
"HeaderFavoriteSongs": "რჩეული სიმღერები",
|
||||||
"HeaderRecordingGroups": "ჩამწერი ჯგუფები",
|
"HeaderRecordingGroups": "ჩამწერი ჯგუფები",
|
||||||
"HearingImpaired": "სმენადაქვეითებული",
|
"HearingImpaired": "სმენადაქვეითებული",
|
||||||
"LabelRunningTimeValue": "ხანგრძლივობა: {0}",
|
"LabelRunningTimeValue": "გაშვებულობის დრო: {0}",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin-ის სერვერი განახლდა {0}-ზე",
|
"MessageApplicationUpdatedTo": "Jellyfin-ის სერვერი განახლდა {0}-ზე",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "სერვერის კონფიგურაციის სექცია {0} განახლდა",
|
"MessageNamedServerConfigurationUpdatedWithValue": "სერვერის კონფიგურაციის სექცია {0} განახლდა",
|
||||||
"MixedContent": "შერეული შემცველობა",
|
"MixedContent": "შერეული შემცველობა",
|
||||||
"MusicVideos": "მუსიკალური ვიდეოები",
|
"MusicVideos": "მუსიკის ვიდეოები",
|
||||||
"NotificationOptionInstallationFailed": "დაყენების შეცდომა",
|
"NotificationOptionInstallationFailed": "დაყენების შეცდომა",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "აპლიკაციის განახლება დაყენებულია",
|
"NotificationOptionApplicationUpdateInstalled": "აპლიკაციის განახლება დაყენებულია",
|
||||||
"NotificationOptionAudioPlayback": "აუდიოს დაკვრა დაწყებულია",
|
"NotificationOptionAudioPlayback": "აუდიოს დაკვრა დაწყებულია",
|
||||||
"NotificationOptionCameraImageUploaded": "კამერის გამოსახულება ატვირთულია",
|
"NotificationOptionCameraImageUploaded": "კამერის გამოსახულება ატვირთულია",
|
||||||
"NotificationOptionVideoPlaybackStopped": "ვიდეოს დაკვრა გაჩერებულია",
|
"NotificationOptionVideoPlaybackStopped": "ვიდეოს დაკვრა გაჩერებულია",
|
||||||
"PluginUninstalledWithName": "{0} წაიშალა",
|
"PluginUninstalledWithName": "{0} წაიშალა",
|
||||||
"ScheduledTaskStartedWithName": "{0} დაიწყო",
|
"ScheduledTaskStartedWithName": "{0} გაეშვა",
|
||||||
"VersionNumber": "ვერსია {0}",
|
"VersionNumber": "ვერსია {0}",
|
||||||
"TasksChannelsCategory": "ინტერნეტ-არხები",
|
"TasksChannelsCategory": "ინტერნეტ-არხები",
|
||||||
"ValueSpecialEpisodeName": "დამატებითი - {0}",
|
"ValueSpecialEpisodeName": "სპეციალური - {0}",
|
||||||
"TaskRefreshChannelsDescription": "ინტერნეტ-არხის ინფორმაციის განახლება.",
|
"TaskRefreshChannelsDescription": "ინტერნეტ-არხის ინფორმაციის განახლება.",
|
||||||
"Channels": "არხები",
|
"Channels": "არხები",
|
||||||
"Collections": "კოლექციები",
|
"Collections": "კოლექციები",
|
||||||
@@ -56,31 +56,31 @@
|
|||||||
"Favorites": "რჩეულები",
|
"Favorites": "რჩეულები",
|
||||||
"Folders": "საქაღალდეები",
|
"Folders": "საქაღალდეები",
|
||||||
"HeaderFavoriteShows": "რჩეული სერიალები",
|
"HeaderFavoriteShows": "რჩეული სერიალები",
|
||||||
"HeaderLiveTV": "ლაივ ტელევიზია",
|
"HeaderLiveTV": "ცოცხალი TV",
|
||||||
"HeaderNextUp": "შემდეგი",
|
"HeaderNextUp": "შემდეგი ზემოთ",
|
||||||
"HomeVideos": "სახლის ვიდეოები",
|
"HomeVideos": "სახლის ვიდეოები",
|
||||||
"NameSeasonNumber": "სეზონი {0}",
|
"NameSeasonNumber": "სეზონი {0}",
|
||||||
"NameSeasonUnknown": "სეზონი უცნობია",
|
"NameSeasonUnknown": "სეზონი უცნობია",
|
||||||
"NotificationOptionPluginError": "მოდულის შეცდომა",
|
"NotificationOptionPluginError": "დამატების შეცდომა",
|
||||||
"NotificationOptionPluginInstalled": "მოდული დაყენებულია",
|
"NotificationOptionPluginInstalled": "დამატება დაყენებულია",
|
||||||
"NotificationOptionPluginUninstalled": "მოდული წაიშალა",
|
"NotificationOptionPluginUninstalled": "დამატება წაიშალა",
|
||||||
"ProviderValue": "მომწოდებელი: {0}",
|
"ProviderValue": "მომწოდებელი: {0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} ვერ შესრულდა",
|
"ScheduledTaskFailedWithName": "{0} ავარიულია",
|
||||||
"TvShows": "სატელევიზიო სერიალები",
|
"TvShows": "TV სერიალები",
|
||||||
"TaskRefreshPeople": "ხალხის განახლება",
|
"TaskRefreshPeople": "ხალხის განახლება",
|
||||||
"TaskUpdatePlugins": "მოდულების განახლება",
|
"TaskUpdatePlugins": "დამატებების განახლება",
|
||||||
"TaskRefreshChannels": "არხების განახლება",
|
"TaskRefreshChannels": "არხების განახლება",
|
||||||
"TaskOptimizeDatabase": "მონაცემთა ბაზის ოპტიმიზაცია",
|
"TaskOptimizeDatabase": "ბაზების ოპტიმიზაცია",
|
||||||
"TaskKeyframeExtractor": "საკვანძო კადრის გამომღები",
|
"TaskKeyframeExtractor": "საკვანძო კადრის გამომღები",
|
||||||
"DeviceOnlineWithName": "{0} დაკავშირდა",
|
"DeviceOnlineWithName": "{0} შეერთებულია",
|
||||||
"LabelIpAddressValue": "IP მისამართი: {0}",
|
"LabelIpAddressValue": "IP მისამართი: {0}",
|
||||||
"NameInstallFailed": "{0}-ის დაყენების შეცდომა",
|
"NameInstallFailed": "{0}-ის დაყენების შეცდომა",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "ხელმისაწვდომია აპლიკაციის განახლება",
|
"NotificationOptionApplicationUpdateAvailable": "ხელმისაწვდომია აპლიკაციის განახლება",
|
||||||
"NotificationOptionAudioPlaybackStopped": "აუდიოს დაკვრა გაჩერებულია",
|
"NotificationOptionAudioPlaybackStopped": "აუდიოს დაკვრა გაჩერებულია",
|
||||||
"NotificationOptionNewLibraryContent": "ახალი შემცველობა დამატებულია",
|
"NotificationOptionNewLibraryContent": "ახალი შემცველობა დამატებულია",
|
||||||
"NotificationOptionPluginUpdateInstalled": "მოდულიs განახლება დაყენებულია",
|
"NotificationOptionPluginUpdateInstalled": "დამატების განახლება დაყენებულია",
|
||||||
"NotificationOptionServerRestartRequired": "საჭიროა სერვერის გადატვირთვა",
|
"NotificationOptionServerRestartRequired": "სერვერის გადატვირთვა აუცილებელია",
|
||||||
"NotificationOptionTaskFailed": "გეგმიური დავალების შეცდომა",
|
"NotificationOptionTaskFailed": "დაგეგმილი ამოცანის შეცდომა",
|
||||||
"NotificationOptionUserLockedOut": "მომხმარებელი დაიბლოკა",
|
"NotificationOptionUserLockedOut": "მომხმარებელი დაიბლოკა",
|
||||||
"NotificationOptionVideoPlayback": "ვიდეოს დაკვრა დაწყებულია",
|
"NotificationOptionVideoPlayback": "ვიდეოს დაკვრა დაწყებულია",
|
||||||
"PluginInstalledWithName": "{0} დაყენებულია",
|
"PluginInstalledWithName": "{0} დაყენებულია",
|
||||||
@@ -91,51 +91,39 @@
|
|||||||
"TaskRefreshLibrary": "მედიის ბიბლიოთეკის სკანირება",
|
"TaskRefreshLibrary": "მედიის ბიბლიოთეკის სკანირება",
|
||||||
"TaskCleanLogs": "ჟურნალის საქაღალდის გასუფთავება",
|
"TaskCleanLogs": "ჟურნალის საქაღალდის გასუფთავება",
|
||||||
"TaskCleanTranscode": "ტრანსკოდირების საქაღალდის გასუფთავება",
|
"TaskCleanTranscode": "ტრანსკოდირების საქაღალდის გასუფთავება",
|
||||||
"TaskDownloadMissingSubtitles": "მიუწვდომელი სუბტიტრების გადმოწერა",
|
"TaskDownloadMissingSubtitles": "ნაკლული სუბტიტრების გადმოწერა",
|
||||||
"UserDownloadingItemWithValues": "{0} -ი {1}-ს იწერს",
|
"UserDownloadingItemWithValues": "{0} -ი {0}-ს იწერს",
|
||||||
"FailedLoginAttemptWithUserName": "შესვლის წარუმატებელი მცდელობა {0}-დან",
|
"FailedLoginAttemptWithUserName": "{0}-დან შემოსვლის შეცდომა",
|
||||||
"MessageApplicationUpdated": "Jellyfin-ის სერვერი განახლდა",
|
"MessageApplicationUpdated": "Jellyfin-ის სერვერი განახლდა",
|
||||||
"MessageServerConfigurationUpdated": "სერვერის კონფიგურაცია განახლდა",
|
"MessageServerConfigurationUpdated": "სერვერის კონფიგურაცია განახლდა",
|
||||||
"ServerNameNeedsToBeRestarted": "საჭიროა {0}-ის გადატვირთვა",
|
"ServerNameNeedsToBeRestarted": "საჭიროა {0}-ის გადატვირთვა",
|
||||||
"UserCreatedWithName": "მომხმარებელი {0} შეიქმნა",
|
"UserCreatedWithName": "მომხმარებელი {0} შეიქმნა",
|
||||||
"UserDeletedWithName": "მომხმარებელი {0} წაშლილია",
|
"UserDeletedWithName": "მომხმარებელი {0} წაშლილია",
|
||||||
"UserOnlineFromDevice": "{0}-ი დაკავშირდა {1}-დან",
|
"UserOnlineFromDevice": "{0}-ი ხაზზეა {1}-დან",
|
||||||
"UserOfflineFromDevice": "{0}-ი {1}-დან გაეთიშა",
|
"UserOfflineFromDevice": "{0}-ი {1}-დან გაითიშა",
|
||||||
"ItemAddedWithName": "{0} ჩამატებულია ბიბლიოთეკაში",
|
"ItemAddedWithName": "{0} ჩამატებულია ბიბლიოთეკაში",
|
||||||
"ItemRemovedWithName": "{0} წაშლილია ბიბლიოთეკიდან",
|
"ItemRemovedWithName": "{0} წაშლილია ბიბლიოთეკიდან",
|
||||||
"UserLockedOutWithName": "მომხმარებელი {0} დაბლოკილია",
|
"UserLockedOutWithName": "მომხმარებელი {0} დაბლოკილია",
|
||||||
"UserStartedPlayingItemWithValues": "{0} უყურებს {1}-ს {2}-ზე",
|
"UserStartedPlayingItemWithValues": "{0} თამაშობს {1}-ს {2}-ზე",
|
||||||
"UserPasswordChangedWithName": "მომხმარებელი {0}-სთვის პაროლი შეიცვალა",
|
"UserPasswordChangedWithName": "მომხმარებლისთვის {0} პაროლი შეცვლილია",
|
||||||
"UserPolicyUpdatedWithName": "{0}-ის მომხმარებლის პოლიტიკა განახლდა",
|
"UserPolicyUpdatedWithName": "{0}-ის მომხმარებლის პოლიტიკა განახლდა",
|
||||||
"UserStoppedPlayingItemWithValues": "{0}-მა დაასრულა {1}-ის ყურება {2}-ზე",
|
"UserStoppedPlayingItemWithValues": "{0}-მა დაამთავრა {1}-ის დაკვრა {2}-ზე",
|
||||||
"TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა.",
|
"TaskRefreshChapterImagesDescription": "თავების მქონე ვიდეოებისთვის მინიატურების შექმნა.",
|
||||||
"TaskKeyframeExtractorDescription": "უფრო ზუსტი HLS დასაკრავი სიებისითვის ვიდეოდან საკვანძო გადრების ამოღება. შეიძლება საკმაო დრო დასჭირდეს.",
|
"TaskKeyframeExtractorDescription": "უფრო ზუსტი HLS დასაკრავი სიებისითვის ვიდეოდან საკვანძო გადრების ამოღება. შეიძლება საკმაო დრო დასჭირდეს.",
|
||||||
"NewVersionIsAvailable": "გადმოსაწერად ხელმისაწვდომია Jellyfin -ის ახალი ვერსია.",
|
"NewVersionIsAvailable": "გადმოსაწერად ხელმისაწვდომია Jellyfin -ის ახალი ვერსია.",
|
||||||
"CameraImageUploadedFrom": "ახალი კამერის გამოსახულება ატვირთულია {0}-დან",
|
"CameraImageUploadedFrom": "ახალი კამერის გამოსახულება ატვირთულია {0}-დან",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin სერვერი იტვირთება. მოგვიანებით სცადეთ.",
|
"StartupEmbyServerIsLoading": "Jellyfin სერვერი იტვირთება. მოგვიანებით სცადეთ.",
|
||||||
"SubtitleDownloadFailureFromForItem": "{0}-დან {1}-სთვის სუბტიტრების გადმოწერა ვერ შესრულდა",
|
"SubtitleDownloadFailureFromForItem": "{0}-დან {1}-სთვის სუბტიტრების გადმოწერის შეცდომა",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} დაემატა თქვენს მედიის ბიბლიოთეკას",
|
"ValueHasBeenAddedToLibrary": "{0} დაემატა თქვენს მედიის ბიბლიოთეკას",
|
||||||
"TaskCleanActivityLogDescription": "შლის მითითებულ ასაკზე ძველ ჟურნალის ჩანაწერებს.",
|
"TaskCleanActivityLogDescription": "მითითებულ ასაკზე ძველი ჟურნალის ჩანაწერების წაშლა.",
|
||||||
"TaskCleanCacheDescription": "შლის სისტემისთვის არასაჭირო ქეშის ფაილებს.",
|
"TaskCleanCacheDescription": "სისტემისთვის არასაჭირო ქეშის ფაილების წაშლა.",
|
||||||
"TaskRefreshLibraryDescription": "ეძებს ახალ ფაილებს თქვენს მედიის ბიბლიოთეკაში და ანახლებს მეტამონაცემებს.",
|
"TaskRefreshLibraryDescription": "თქვენი მედია ბიბლიოთეკაში ახალი ფაილების ძებნა და მეტამონაცემების განახლება.",
|
||||||
"TaskCleanLogsDescription": "{0} დღეზე ძველი ჟურნალის ფაილების წაშლა.",
|
"TaskCleanLogsDescription": "{0} დღეზე ძველი ჟურნალის ფაილების წაშლა.",
|
||||||
"TaskRefreshPeopleDescription": "თქვენს მედიის ბიბლიოთეკაში მსახიობების და რეჟისორების მეტამონაცემების განახლება.",
|
"TaskRefreshPeopleDescription": "თქვენს მედიის ბიბლიოთეკაში მსახიობების და რეჟისორების მეტამონაცემების განახლება.",
|
||||||
"TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული მოდულების განახლებების გადმოწერა და დაყენება.",
|
"TaskUpdatePluginsDescription": "ავტომატურად განახლებადად მონიშნული დამატებების განახლებების გადმოწერა და დაყენება.",
|
||||||
"TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.",
|
"TaskCleanTranscodeDescription": "ერთ დღეზე უფრო ძველი ტრანსკოდირების ფაილების წაშლა.",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "ეძებს ბიბლიოთეკაში მიუწვდომელ სუბტიტრებს ინტერნეტში მეტამონაცემებზე დაყრდნობით.",
|
"TaskDownloadMissingSubtitlesDescription": "მეტამონაცემებზე დაყრდნობით ინტერნეტში ნაკლული სუბტიტრების ძებნა.",
|
||||||
"TaskOptimizeDatabaseDescription": "კუმშავს მონაცემთა ბაზას ადგილის გათავისუფლებლად. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს.",
|
"TaskOptimizeDatabaseDescription": "ბაზს შეკუშვა და ადგილის გათავისუფლება. ამ ამოცანის ბიბლიოთეკის სკანირების ან ნებისმიერი ცვლილების, რომელიც ბაზაში რამეს აკეთებს, გაშვებას შეუძლია ბაზის წარმადობა გაზარდოს.",
|
||||||
"TaskRefreshTrickplayImagesDescription": "ქმნის trickplay წინასწარ ხედებს ვიდეოებისთვის დაშვებულ ბიბლიოთეკებში.",
|
"TaskRefreshTrickplayImagesDescription": "ქმნის trickplay წინასწარ ხედებს ვიდეოებისთვის ჩართულ ბიბლიოთეკებში.",
|
||||||
"TaskRefreshTrickplayImages": "Trickplay სურათების გენერირება",
|
"TaskRefreshTrickplayImages": "Trickplay სურათების გენერირება"
|
||||||
"TaskAudioNormalization": "აუდიოს ნორმალიზება",
|
|
||||||
"TaskAudioNormalizationDescription": "აანალიზებს ფაილებს აუდიოს ნორმალიზაციისთვის.",
|
|
||||||
"TaskDownloadMissingLyrics": "მიუწვდომელი ლირიკების ჩამოტვირთვა",
|
|
||||||
"TaskDownloadMissingLyricsDescription": "ჩამოტვირთავს ამჟამად ბიბლიოთეკაში არარსებულ ლირიკებს სიმღერებისთვის",
|
|
||||||
"TaskCleanCollectionsAndPlaylists": "კოლექციების და დასაკრავი სიების გასუფთავება",
|
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "შლის არარსებულ ერთეულებს კოლექციებიდან და დასაკრავი სიებიდან.",
|
|
||||||
"TaskExtractMediaSegments": "მედია სეგმენტების სკანირება",
|
|
||||||
"TaskExtractMediaSegmentsDescription": "მედია სეგმენტების სკანირება მხარდაჭერილი მოდულებისთვის.",
|
|
||||||
"TaskMoveTrickplayImages": "Trickplay სურათების მიგრაცია",
|
|
||||||
"TaskMoveTrickplayImagesDescription": "გადააქვს trickplay ფაილები ბიბლიოთეკის პარამეტრებზე დაყრდნობით.",
|
|
||||||
"CleanupUserDataTask": "მომხმარებლების მონაცემების გასუფთავება",
|
|
||||||
"CleanupUserDataTaskDescription": "ასუფთავებს მომხმარებლების მონაცემებს (ყურების სტატუსი, ფავორიტები ანდ ა.შ) მედია ელემენტებისთვის რომლების 90 დღეზე მეტია აღარ არსებობენ."
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Albums": "Albums",
|
||||||
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
"AppDeviceValues": "App: {0}, Apparaat: {1}",
|
||||||
"Application": "Applicatie",
|
"Application": "Applicatie",
|
||||||
"Artists": "Artiesten",
|
"Artists": "Artiesten",
|
||||||
@@ -13,18 +14,19 @@
|
|||||||
"FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
|
"FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
|
||||||
"Favorites": "Favorieten",
|
"Favorites": "Favorieten",
|
||||||
"Folders": "Mappen",
|
"Folders": "Mappen",
|
||||||
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Albumartiesten",
|
"HeaderAlbumArtists": "Albumartiesten",
|
||||||
"HeaderContinueWatching": "Verder kijken",
|
"HeaderContinueWatching": "Verderkijken",
|
||||||
"HeaderFavoriteAlbums": "Favoriete albums",
|
"HeaderFavoriteAlbums": "Favoriete albums",
|
||||||
"HeaderFavoriteArtists": "Favoriete artiesten",
|
"HeaderFavoriteArtists": "Favoriete artiesten",
|
||||||
"HeaderFavoriteEpisodes": "Favoriete afleveringen",
|
"HeaderFavoriteEpisodes": "Favoriete afleveringen",
|
||||||
"HeaderFavoriteShows": "Favoriete series",
|
"HeaderFavoriteShows": "Favoriete series",
|
||||||
"HeaderFavoriteSongs": "Favoriete nummers",
|
"HeaderFavoriteSongs": "Favoriete nummers",
|
||||||
"HeaderLiveTV": "Live-tv",
|
"HeaderLiveTV": "Live-tv",
|
||||||
"HeaderNextUp": "Volgende",
|
"HeaderNextUp": "Als volgende",
|
||||||
"HeaderRecordingGroups": "Opnamegroepen",
|
"HeaderRecordingGroups": "Opnamegroepen",
|
||||||
"HomeVideos": "Homevideo's",
|
"HomeVideos": "Homevideo's",
|
||||||
"Inherit": "Overnemen",
|
"Inherit": "Erven",
|
||||||
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
"ItemAddedWithName": "{0} is toegevoegd aan de bibliotheek",
|
||||||
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
"ItemRemovedWithName": "{0} is verwijderd uit de bibliotheek",
|
||||||
"LabelIpAddressValue": "IP-adres: {0}",
|
"LabelIpAddressValue": "IP-adres: {0}",
|
||||||
@@ -114,7 +116,7 @@
|
|||||||
"TaskCleanActivityLogDescription": "Verwijdert activiteitenlogs ouder dan de ingestelde leeftijd.",
|
"TaskCleanActivityLogDescription": "Verwijdert activiteitenlogs ouder dan de ingestelde leeftijd.",
|
||||||
"TaskCleanActivityLog": "Activiteitenlogboek legen",
|
"TaskCleanActivityLog": "Activiteitenlogboek legen",
|
||||||
"Undefined": "Niet gedefinieerd",
|
"Undefined": "Niet gedefinieerd",
|
||||||
"Forced": "Geforceerd",
|
"Forced": "Gedwongen",
|
||||||
"Default": "Standaard",
|
"Default": "Standaard",
|
||||||
"TaskOptimizeDatabaseDescription": "Comprimeert de database en trimt vrije ruimte. Het uitvoeren van deze taak kan de prestaties verbeteren, na het scannen van de bibliotheek of andere aanpassingen die invloed hebben op de database.",
|
"TaskOptimizeDatabaseDescription": "Comprimeert de database en trimt vrije ruimte. Het uitvoeren van deze taak kan de prestaties verbeteren, na het scannen van de bibliotheek of andere aanpassingen die invloed hebben op de database.",
|
||||||
"TaskOptimizeDatabase": "Database optimaliseren",
|
"TaskOptimizeDatabase": "Database optimaliseren",
|
||||||
@@ -135,7 +137,5 @@
|
|||||||
"TaskMoveTrickplayImagesDescription": "Verplaatst bestaande trickplay-bestanden op basis van de bibliotheekinstellingen.",
|
"TaskMoveTrickplayImagesDescription": "Verplaatst bestaande trickplay-bestanden op basis van de bibliotheekinstellingen.",
|
||||||
"TaskExtractMediaSegments": "Scannen op mediasegmenten",
|
"TaskExtractMediaSegments": "Scannen op mediasegmenten",
|
||||||
"CleanupUserDataTaskDescription": "Wist alle gebruikersgegevens (kijkstatus, favorieten, etc.) van media die al minstens 90 dagen niet meer aanwezig zijn.",
|
"CleanupUserDataTaskDescription": "Wist alle gebruikersgegevens (kijkstatus, favorieten, etc.) van media die al minstens 90 dagen niet meer aanwezig zijn.",
|
||||||
"CleanupUserDataTask": "Opruimtaak gebruikersdata",
|
"CleanupUserDataTask": "Opruimtaak gebruikersdata"
|
||||||
"Albums": "Albums",
|
|
||||||
"Genres": "Genres"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,8 +124,8 @@
|
|||||||
"TaskKeyframeExtractor": "Extrator de Quadros-chave",
|
"TaskKeyframeExtractor": "Extrator de Quadros-chave",
|
||||||
"External": "Externo",
|
"External": "Externo",
|
||||||
"HearingImpaired": "Surdo",
|
"HearingImpaired": "Surdo",
|
||||||
"TaskRefreshTrickplayImages": "Gerar imagens de trickplay",
|
"TaskRefreshTrickplayImages": "Gerar Imagens de Trickplay",
|
||||||
"TaskRefreshTrickplayImagesDescription": "Cria pré-visualizações de trickplay para vídeos nas bibliotecas ativadas.",
|
"TaskRefreshTrickplayImagesDescription": "Cria ficheiros de trickplay para vídeos nas bibliotecas ativas.",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
|
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
|
||||||
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
|
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
|
||||||
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
|
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
|
||||||
|
|||||||
@@ -124,8 +124,8 @@
|
|||||||
"HearingImpaired": "Problemas auditivos",
|
"HearingImpaired": "Problemas auditivos",
|
||||||
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
||||||
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.",
|
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.",
|
||||||
"TaskRefreshTrickplayImages": "Gerar imagens de trickplay",
|
"TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo",
|
||||||
"TaskRefreshTrickplayImagesDescription": "Cria pré-visualizações de trickplay para vídeos nas bibliotecas ativadas.",
|
"TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas.",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
|
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
|
||||||
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
|
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
|
||||||
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
|
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}",
|
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}",
|
||||||
"Sync": "Synk",
|
"Sync": "Synk",
|
||||||
"System": "System",
|
"System": "System",
|
||||||
"TvShows": "Tv-serier",
|
"TvShows": "TV-serier",
|
||||||
"User": "Användare",
|
"User": "Användare",
|
||||||
"UserCreatedWithName": "Användaren {0} har skapats",
|
"UserCreatedWithName": "Användaren {0} har skapats",
|
||||||
"UserDeletedWithName": "Användaren {0} har tagits bort",
|
"UserDeletedWithName": "Användaren {0} har tagits bort",
|
||||||
|
|||||||
@@ -124,17 +124,17 @@
|
|||||||
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
|
"TaskKeyframeExtractor": "Екстрактор ключових кадрів",
|
||||||
"External": "Зовнішній",
|
"External": "Зовнішній",
|
||||||
"HearingImpaired": "З порушеннями слуху",
|
"HearingImpaired": "З порушеннями слуху",
|
||||||
"TaskRefreshTrickplayImagesDescription": "Створює прев'ю-зображення для відео у ввімкнених медіатеках.",
|
"TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.",
|
||||||
"TaskRefreshTrickplayImages": "Створити Прев'ю-зображення",
|
"TaskRefreshTrickplayImages": "Створити Trickplay-зображення",
|
||||||
"TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення",
|
"TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують.",
|
"TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують.",
|
||||||
"TaskAudioNormalizationDescription": "Сканує файли на наявність даних для нормалізації звуку.",
|
"TaskAudioNormalizationDescription": "Сканує файли на наявність даних для нормалізації звуку.",
|
||||||
"TaskAudioNormalization": "Нормалізація аудіо",
|
"TaskAudioNormalization": "Нормалізація аудіо",
|
||||||
"TaskDownloadMissingLyrics": "Завантажити відсутні тексти пісень",
|
"TaskDownloadMissingLyrics": "Завантажити відсутні тексти пісень",
|
||||||
"TaskDownloadMissingLyricsDescription": "Завантаження текстів пісень",
|
"TaskDownloadMissingLyricsDescription": "Завантаження текстів пісень",
|
||||||
"TaskMoveTrickplayImagesDescription": "Переміщує наявні прев'ю-зображення відповідно до налаштувань медіатеки.",
|
"TaskMoveTrickplayImagesDescription": "Переміщує наявні Trickplay-зображення відповідно до налаштувань медіатеки.",
|
||||||
"TaskExtractMediaSegments": "Сканування медіа-сегментів",
|
"TaskExtractMediaSegments": "Сканування медіа-сегментів",
|
||||||
"TaskMoveTrickplayImages": "Змінити місце розташування прев'ю-зображень",
|
"TaskMoveTrickplayImages": "Змінити місце розташування Trickplay-зображень",
|
||||||
"TaskExtractMediaSegmentsDescription": "Витягує або отримує медіа-сегменти з плагінів з підтримкою MediaSegment.",
|
"TaskExtractMediaSegmentsDescription": "Витягує або отримує медіа-сегменти з плагінів з підтримкою MediaSegment.",
|
||||||
"CleanupUserDataTask": "Завдання очищення даних користувача",
|
"CleanupUserDataTask": "Завдання очищення даних користувача",
|
||||||
"CleanupUserDataTaskDescription": "Очищає всі дані користувача (стан перегляду, статус обраного тощо) з медіа, які перестали бути доступними щонайменше 90 днів тому."
|
"CleanupUserDataTaskDescription": "Очищає всі дані користувача (стан перегляду, статус обраного тощо) з медіа, які перестали бути доступними щонайменше 90 днів тому."
|
||||||
|
|||||||
@@ -1,141 +1,141 @@
|
|||||||
{
|
{
|
||||||
"Albums": "專輯",
|
"Albums": "專輯",
|
||||||
"AppDeviceValues": "程式:{0},裝置:{1}",
|
"AppDeviceValues": "程式:{0},設備:{1}",
|
||||||
"Application": "應用程式",
|
"Application": "應用程式",
|
||||||
"Artists": "藝人",
|
"Artists": "藝人",
|
||||||
"AuthenticationSucceededWithUserName": "{0} 成功通過驗證",
|
"AuthenticationSucceededWithUserName": "成功授權 {0}",
|
||||||
"Books": "書籍",
|
"Books": "書籍",
|
||||||
"CameraImageUploadedFrom": "{0} 已經成功上載咗一張新相",
|
"CameraImageUploadedFrom": "{0} 成功上傳一張新照片",
|
||||||
"Channels": "頻道",
|
"Channels": "頻道",
|
||||||
"ChapterNameValue": "第 {0} 章",
|
"ChapterNameValue": "第 {0} 章",
|
||||||
"Collections": "系列",
|
"Collections": "系列",
|
||||||
"DeviceOfflineWithName": "{0} 斷開咗連線",
|
"DeviceOfflineWithName": "{0} 已中斷連接",
|
||||||
"DeviceOnlineWithName": "{0} 連線咗",
|
"DeviceOnlineWithName": "{0} 已連接",
|
||||||
"FailedLoginAttemptWithUserName": "來自 {0} 嘅登入嘗試失敗咗",
|
"FailedLoginAttemptWithUserName": "{0} 登入失敗",
|
||||||
"Favorites": "心水",
|
"Favorites": "我的最愛",
|
||||||
"Folders": "資料夾",
|
"Folders": "資料夾",
|
||||||
"Genres": "風格",
|
"Genres": "風格",
|
||||||
"HeaderAlbumArtists": "專輯歌手",
|
"HeaderAlbumArtists": "專輯歌手",
|
||||||
"HeaderContinueWatching": "繼續睇返",
|
"HeaderContinueWatching": "繼續觀看",
|
||||||
"HeaderFavoriteAlbums": "心水嘅專輯",
|
"HeaderFavoriteAlbums": "最愛的專輯",
|
||||||
"HeaderFavoriteArtists": "心水嘅藝人",
|
"HeaderFavoriteArtists": "最愛的藝人",
|
||||||
"HeaderFavoriteEpisodes": "心水嘅劇集",
|
"HeaderFavoriteEpisodes": "最愛的劇集",
|
||||||
"HeaderFavoriteShows": "心水嘅節目",
|
"HeaderFavoriteShows": "最愛的節目",
|
||||||
"HeaderFavoriteSongs": "心水嘅歌曲",
|
"HeaderFavoriteSongs": "最愛的歌曲",
|
||||||
"HeaderLiveTV": "電視直播",
|
"HeaderLiveTV": "電視直播",
|
||||||
"HeaderNextUp": "跟住落嚟",
|
"HeaderNextUp": "繼續觀看",
|
||||||
"HeaderRecordingGroups": "錄製組",
|
"HeaderRecordingGroups": "錄製組",
|
||||||
"HomeVideos": "家庭影片",
|
"HomeVideos": "家庭影片",
|
||||||
"Inherit": "繼承",
|
"Inherit": "繼承",
|
||||||
"ItemAddedWithName": "{0} 經已加咗入媒體櫃",
|
"ItemAddedWithName": "{0} 已被加入至媒體庫",
|
||||||
"ItemRemovedWithName": "{0} 經已由媒體櫃移除咗",
|
"ItemRemovedWithName": "{0} 已從媒體庫移除",
|
||||||
"LabelIpAddressValue": "IP 地址:{0}",
|
"LabelIpAddressValue": "IP 地址:{0}",
|
||||||
"LabelRunningTimeValue": "運行時間:{0}",
|
"LabelRunningTimeValue": "運作時間:{0}",
|
||||||
"Latest": "最新",
|
"Latest": "最新",
|
||||||
"MessageApplicationUpdated": "Jellyfin 經已更新咗",
|
"MessageApplicationUpdated": "Jellyfin 已被更新",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin 已經更新到 {0} 版本",
|
"MessageApplicationUpdatedTo": "Jellyfin 已被更新至 {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定「{0}」經已更新咗",
|
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已被更新",
|
||||||
"MessageServerConfigurationUpdated": "伺服器設定經已更新咗",
|
"MessageServerConfigurationUpdated": "已更新伺服器設定",
|
||||||
"MixedContent": "混合內容",
|
"MixedContent": "混合內容",
|
||||||
"Movies": "電影",
|
"Movies": "電影",
|
||||||
"Music": "音樂",
|
"Music": "音樂",
|
||||||
"MusicVideos": "MV",
|
"MusicVideos": "MV",
|
||||||
"NameInstallFailed": "{0} 安裝失敗",
|
"NameInstallFailed": "{0} 安裝失敗",
|
||||||
"NameSeasonNumber": "第 {0} 季",
|
"NameSeasonNumber": "第 {0} 季",
|
||||||
"NameSeasonUnknown": "未知嘅季度",
|
"NameSeasonUnknown": "未知的季度",
|
||||||
"NewVersionIsAvailable": "有新版本嘅 Jellyfin 可以下載。",
|
"NewVersionIsAvailable": "有新版本的 Jellyfin 可供下載。",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "有得更新應用程式",
|
"NotificationOptionApplicationUpdateAvailable": "有可用的更新",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "應用程式更新好咗",
|
"NotificationOptionApplicationUpdateInstalled": "完成更新應用程式",
|
||||||
"NotificationOptionAudioPlayback": "開始播放音訊",
|
"NotificationOptionAudioPlayback": "播放音訊",
|
||||||
"NotificationOptionAudioPlaybackStopped": "停咗播放音訊",
|
"NotificationOptionAudioPlaybackStopped": "停止播放音訊",
|
||||||
"NotificationOptionCameraImageUploaded": "相機相片上載咗",
|
"NotificationOptionCameraImageUploaded": "相片上傳",
|
||||||
"NotificationOptionInstallationFailed": "安裝失敗",
|
"NotificationOptionInstallationFailed": "安裝失敗",
|
||||||
"NotificationOptionNewLibraryContent": "加咗新內容",
|
"NotificationOptionNewLibraryContent": "新增媒體",
|
||||||
"NotificationOptionPluginError": "外掛程式錯誤",
|
"NotificationOptionPluginError": "插件錯誤",
|
||||||
"NotificationOptionPluginInstalled": "安裝外掛程式",
|
"NotificationOptionPluginInstalled": "安裝插件",
|
||||||
"NotificationOptionPluginUninstalled": "解除安裝外掛程式",
|
"NotificationOptionPluginUninstalled": "解除安裝插件",
|
||||||
"NotificationOptionPluginUpdateInstalled": "外掛程式更新好咗",
|
"NotificationOptionPluginUpdateInstalled": "完成更新插件",
|
||||||
"NotificationOptionServerRestartRequired": "伺服器需要重新啟動",
|
"NotificationOptionServerRestartRequired": "伺服器需要重啟",
|
||||||
"NotificationOptionTaskFailed": "排程工作失敗",
|
"NotificationOptionTaskFailed": "排程工作執行失敗",
|
||||||
"NotificationOptionUserLockedOut": "用家被鎖定咗",
|
"NotificationOptionUserLockedOut": "封鎖用戶",
|
||||||
"NotificationOptionVideoPlayback": "開始播放影片",
|
"NotificationOptionVideoPlayback": "播放影片",
|
||||||
"NotificationOptionVideoPlaybackStopped": "停咗播放影片",
|
"NotificationOptionVideoPlaybackStopped": "停止播放影片",
|
||||||
"Photos": "相片",
|
"Photos": "相片",
|
||||||
"Playlists": "播放清單",
|
"Playlists": "播放清單",
|
||||||
"Plugin": "外掛程式",
|
"Plugin": "插件",
|
||||||
"PluginInstalledWithName": "裝好咗 {0}",
|
"PluginInstalledWithName": "已安裝 {0}",
|
||||||
"PluginUninstalledWithName": "剷走咗 {0}",
|
"PluginUninstalledWithName": "已移除 {0}",
|
||||||
"PluginUpdatedWithName": "更新好咗 {0}",
|
"PluginUpdatedWithName": "已更新 {0}",
|
||||||
"ProviderValue": "提供者:{0}",
|
"ProviderValue": "提供者:{0}",
|
||||||
"ScheduledTaskFailedWithName": "{0} 執行失敗",
|
"ScheduledTaskFailedWithName": "{0} 執行失敗",
|
||||||
"ScheduledTaskStartedWithName": "開始執行 {0}",
|
"ScheduledTaskStartedWithName": "開始執行 {0}",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} 需要重新啟動",
|
"ServerNameNeedsToBeRestarted": "{0} 需要重啟",
|
||||||
"Shows": "節目",
|
"Shows": "節目",
|
||||||
"Songs": "歌曲",
|
"Songs": "歌曲",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin 伺服器載入緊,唔該稍後再試。",
|
"StartupEmbyServerIsLoading": "正在載入 Jellyfin,請稍後再試。",
|
||||||
"SubtitleDownloadFailureFromForItem": "經 {0} 下載 {1} 嘅字幕失敗咗",
|
"SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕",
|
||||||
"Sync": "同步",
|
"Sync": "同步",
|
||||||
"System": "系統",
|
"System": "系統",
|
||||||
"TvShows": "電視節目",
|
"TvShows": "電視節目",
|
||||||
"User": "使用者",
|
"User": "用戶",
|
||||||
"UserCreatedWithName": "經已建立咗新使用者 {0}",
|
"UserCreatedWithName": "建立新用戶 {0}",
|
||||||
"UserDeletedWithName": "使用者 {0} 經已被刪走",
|
"UserDeletedWithName": "用戶 {0} 已被移除",
|
||||||
"UserDownloadingItemWithValues": "{0} 下載緊 {1}",
|
"UserDownloadingItemWithValues": "{0} 正在下載 {1}",
|
||||||
"UserLockedOutWithName": "使用者 {0} 經已被鎖定",
|
"UserLockedOutWithName": "用戶 {0} 已被封鎖",
|
||||||
"UserOfflineFromDevice": "{0} 經已由 {1} 斷開咗連線",
|
"UserOfflineFromDevice": "{0} 終止了 {1} 的連接",
|
||||||
"UserOnlineFromDevice": "{0} 正喺 {1} 連線",
|
"UserOnlineFromDevice": "{0} 從 {1} 連線",
|
||||||
"UserPasswordChangedWithName": "使用者 {0} 嘅密碼經已更改咗",
|
"UserPasswordChangedWithName": "{0} 的密碼已被更改",
|
||||||
"UserPolicyUpdatedWithName": "使用者 {0} 嘅權限經已更新咗",
|
"UserPolicyUpdatedWithName": "使用條款已更新為 {0}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} 正喺 {2} 播緊 {1}",
|
"UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} 已經喺 {2} 停止播放 {1}",
|
"UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} 已經成功加入咗你嘅媒體櫃",
|
"ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫",
|
||||||
"ValueSpecialEpisodeName": "特輯 - {0}",
|
"ValueSpecialEpisodeName": "特典 - {0}",
|
||||||
"VersionNumber": "版本 {0}",
|
"VersionNumber": "版本 {0}",
|
||||||
"TaskDownloadMissingSubtitles": "下載漏咗嘅字幕",
|
"TaskDownloadMissingSubtitles": "下載欠缺字幕",
|
||||||
"TaskUpdatePlugins": "更新外掛程式",
|
"TaskUpdatePlugins": "更新插件",
|
||||||
"TasksApplicationCategory": "應用程式",
|
"TasksApplicationCategory": "應用程式",
|
||||||
"TaskRefreshLibraryDescription": "掃描媒體櫃嚟搵新檔案,同時重新載入媒體詳細資料。",
|
"TaskRefreshLibraryDescription": "掃描媒體庫以加入新增的檔案及重新載入元數據。",
|
||||||
"TasksMaintenanceCategory": "維護",
|
"TasksMaintenanceCategory": "維護",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "根據媒體詳細資料設定,喺網上幫你搵返啲欠缺嘅字幕。",
|
"TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在網上搜尋欠缺的字幕。",
|
||||||
"TaskRefreshChannelsDescription": "重新整理網上頻道嘅資訊。",
|
"TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。",
|
||||||
"TaskRefreshChannels": "重新載入頻道",
|
"TaskRefreshChannels": "重新載入頻道",
|
||||||
"TaskCleanTranscodeDescription": "自動刪走超過一日嘅轉碼檔案。",
|
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
|
||||||
"TaskCleanTranscode": "清理轉碼資料夾",
|
"TaskCleanTranscode": "清理轉碼檔資料夾",
|
||||||
"TaskUpdatePluginsDescription": "自動幫嗰啲設咗要自動更新嘅外掛程式進行下載同安裝。",
|
"TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。",
|
||||||
"TaskRefreshPeopleDescription": "更新媒體櫃入面演員同導演嘅媒體詳細資料。",
|
"TaskRefreshPeopleDescription": "更新你的媒體中有關的演員和導演的元數據。",
|
||||||
"TaskCleanLogsDescription": "自動刪走超過 {0} 日嘅紀錄檔。",
|
"TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。",
|
||||||
"TaskCleanLogs": "清理日誌資料夾",
|
"TaskCleanLogs": "清理紀錄檔資料夾",
|
||||||
"TaskRefreshLibrary": "掃描媒體櫃",
|
"TaskRefreshLibrary": "掃描媒體庫",
|
||||||
"TaskRefreshChapterImagesDescription": "幫有章節嘅影片整返啲章節縮圖。",
|
"TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。",
|
||||||
"TaskRefreshChapterImages": "擷取章節圖片",
|
"TaskRefreshChapterImages": "提取章節圖像",
|
||||||
"TaskCleanCacheDescription": "刪走系統已經唔再需要嘅快取檔案。",
|
"TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。",
|
||||||
"TaskCleanCache": "清理快取(Cache)資料夾",
|
"TaskCleanCache": "清理緩存資料夾",
|
||||||
"TasksChannelsCategory": "網路頻道",
|
"TasksChannelsCategory": "網絡頻道",
|
||||||
"TasksLibraryCategory": "媒體櫃",
|
"TasksLibraryCategory": "媒體庫",
|
||||||
"TaskRefreshPeople": "重新載入人物",
|
"TaskRefreshPeople": "重新載入人物",
|
||||||
"TaskCleanActivityLog": "清理活動紀錄",
|
"TaskCleanActivityLog": "清理活動記錄",
|
||||||
"Undefined": "未定義",
|
"Undefined": "未定義",
|
||||||
"Forced": "強制",
|
"Forced": "強制",
|
||||||
"Default": "初始",
|
"Default": "預設",
|
||||||
"TaskOptimizeDatabaseDescription": "壓縮數據櫃並釋放剩餘空間。喺掃描媒體櫃或者做咗一啲會修改數據櫃嘅操作之後行呢個任務,或者可以提升效能。",
|
"TaskOptimizeDatabaseDescription": "壓縮數據庫及釋放可用空間。完成任何會修改數據庫的工作(例如掃描媒體庫)後,執行此工作或可提升伺服器速度。",
|
||||||
"TaskOptimizeDatabase": "最佳化數據櫃",
|
"TaskOptimizeDatabase": "最佳化數據庫",
|
||||||
"TaskCleanActivityLogDescription": "刪走超過設定日期嘅活動記錄。",
|
"TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。",
|
||||||
"TaskKeyframeExtractorDescription": "提取關鍵影格(Keyframe)嚟建立更準確嘅 HLS 播放列表。呢個任務可能要行好耐。",
|
"TaskKeyframeExtractorDescription": "提取關鍵影格(Keyframe)以建立更準確的 HLS playlist。此工作可能需要使用較長時間來完成。",
|
||||||
"TaskKeyframeExtractor": "關鍵影格提取器",
|
"TaskKeyframeExtractor": "關鍵影格提取器",
|
||||||
"External": "外部",
|
"External": "外部",
|
||||||
"HearingImpaired": "聽力障礙",
|
"HearingImpaired": "聽力障礙",
|
||||||
"TaskRefreshTrickplayImages": "產生搜畫預覽圖",
|
"TaskRefreshTrickplayImages": "建立 Trickplay 圖像",
|
||||||
"TaskRefreshTrickplayImagesDescription": "為已啟用功能嘅媒體櫃影片製作快轉預覽圖。",
|
"TaskRefreshTrickplayImagesDescription": "為已啟用 Trickplay 的媒體庫內的影片建立 Trickplay 預覽圖。",
|
||||||
"TaskExtractMediaSegments": "掃描媒體分段資訊",
|
"TaskExtractMediaSegments": "掃描媒體分段資訊",
|
||||||
"TaskExtractMediaSegmentsDescription": "從支援 MediaSegment 功能嘅外掛程式入面提取媒體片段。",
|
"TaskExtractMediaSegmentsDescription": "從允許MediaSegment 功能的插件中獲取媒體片段。",
|
||||||
"TaskDownloadMissingLyrics": "下載缺失嘅歌詞",
|
"TaskDownloadMissingLyrics": "下載欠缺歌詞",
|
||||||
"TaskDownloadMissingLyricsDescription": "幫啲歌下載歌詞",
|
"TaskDownloadMissingLyricsDescription": "下載歌詞",
|
||||||
"TaskCleanCollectionsAndPlaylists": "清理媒體系列(Collections)同埋播放清單",
|
"TaskCleanCollectionsAndPlaylists": "整理媒體與播放清單",
|
||||||
"TaskAudioNormalization": "音訊同等化",
|
"TaskAudioNormalization": "音訊同等化",
|
||||||
"TaskAudioNormalizationDescription": "掃描檔案入面嘅音訊標准化(Audio Normalization)數據。",
|
"TaskAudioNormalizationDescription": "掃描檔案裏的音訊同等化資料。",
|
||||||
"TaskCleanCollectionsAndPlaylistsDescription": "自動清理資料庫同播放清單入面已經唔存在嘅項目。",
|
"TaskCleanCollectionsAndPlaylistsDescription": "從資料庫及播放清單中移除已不存在的項目。",
|
||||||
"TaskMoveTrickplayImagesDescription": "根據媒體櫃設定,將現有嘅 Trickplay(快轉預覽)檔案搬去對應位置。",
|
"TaskMoveTrickplayImagesDescription": "根據媒體庫設定移動現有的 Trickplay 檔案。",
|
||||||
"TaskMoveTrickplayImages": "搬移快轉預覽圖嘅位置",
|
"TaskMoveTrickplayImages": "轉移 Trickplay 影像位置",
|
||||||
"CleanupUserDataTask": "清理使用者資料嘅任務",
|
"CleanupUserDataTask": "用戶資料清理工作",
|
||||||
"CleanupUserDataTaskDescription": "清理已消失至少 90 日嘅媒體用家數據(包括觀看狀態、心水狀態等)。"
|
"CleanupUserDataTaskDescription": "從用戶數據中清除已經被刪除超過 90 日的媒體相關資料。"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,22 +198,17 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
return Playlist.GetPlaylistItems(items, user, options);
|
return Playlist.GetPlaylistItems(items, user, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, int? position, Guid userId)
|
public Task AddItemToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
|
||||||
{
|
{
|
||||||
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
|
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
|
||||||
|
|
||||||
return AddToPlaylistInternal(
|
return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
|
||||||
playlistId,
|
|
||||||
itemIds,
|
|
||||||
user,
|
|
||||||
new DtoOptions(false)
|
|
||||||
{
|
{
|
||||||
EnableImages = true
|
EnableImages = true
|
||||||
},
|
});
|
||||||
position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOptions options, int? position = null)
|
private async Task AddToPlaylistInternal(Guid playlistId, IReadOnlyCollection<Guid> newItemIds, User user, DtoOptions options)
|
||||||
{
|
{
|
||||||
// Retrieve the existing playlist
|
// Retrieve the existing playlist
|
||||||
var playlist = _libraryManager.GetItemById(playlistId) as Playlist
|
var playlist = _libraryManager.GetItemById(playlistId) as Playlist
|
||||||
@@ -248,30 +243,7 @@ namespace Emby.Server.Implementations.Playlists
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the playlist in the repository
|
// Update the playlist in the repository
|
||||||
if (position.HasValue)
|
|
||||||
{
|
|
||||||
if (position.Value <= 0)
|
|
||||||
{
|
|
||||||
playlist.LinkedChildren = [.. childrenToAdd, .. playlist.LinkedChildren];
|
|
||||||
}
|
|
||||||
else if (position.Value >= playlist.LinkedChildren.Length)
|
|
||||||
{
|
|
||||||
playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
|
playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playlist.LinkedChildren = [
|
|
||||||
.. playlist.LinkedChildren[0..position.Value],
|
|
||||||
.. childrenToAdd,
|
|
||||||
.. playlist.LinkedChildren[position.Value..playlist.LinkedChildren.Length]
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playlist.LinkedChildren = [.. playlist.LinkedChildren, .. childrenToAdd];
|
|
||||||
}
|
|
||||||
|
|
||||||
playlist.DateLastMediaAdded = DateTime.UtcNow;
|
playlist.DateLastMediaAdded = DateTime.UtcNow;
|
||||||
|
|
||||||
await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
|
await UpdatePlaylistInternal(playlist).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -960,7 +960,7 @@ namespace Emby.Server.Implementations.Session
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tracksChanged = UpdatePlaybackSettings(user, info, data);
|
var tracksChanged = UpdatePlaybackSettings(user, info, data);
|
||||||
if (tracksChanged)
|
if (!tracksChanged)
|
||||||
{
|
{
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ public class OfficialRatingComparer : IBaseItemComparer
|
|||||||
ArgumentNullException.ThrowIfNull(y);
|
ArgumentNullException.ThrowIfNull(y);
|
||||||
var zeroRating = new ParentalRatingScore(0, 0);
|
var zeroRating = new ParentalRatingScore(0, 0);
|
||||||
|
|
||||||
var ratingX = string.IsNullOrEmpty(x.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(x.OfficialRating, x.GetPreferredMetadataCountryCode()) ?? zeroRating;
|
var ratingX = string.IsNullOrEmpty(x.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(x.OfficialRating) ?? zeroRating;
|
||||||
var ratingY = string.IsNullOrEmpty(y.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(y.OfficialRating, y.GetPreferredMetadataCountryCode()) ?? zeroRating;
|
var ratingY = string.IsNullOrEmpty(y.OfficialRating) ? zeroRating : _localizationManager.GetRatingScore(y.OfficialRating) ?? zeroRating;
|
||||||
var scoreCompare = ratingX.Score.CompareTo(ratingY.Score);
|
var scoreCompare = ratingX.Score.CompareTo(ratingY.Score);
|
||||||
if (scoreCompare is 0)
|
if (scoreCompare is 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -187,7 +187,39 @@ public class ArtistsController : BaseJellyfinApiController
|
|||||||
}).Where(i => i is not null).Select(i => i!.Id).ToArray();
|
}).Where(i => i is not null).Select(i => i!.Id).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ApplyFilters(filters);
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case ItemFilter.Dislikes:
|
||||||
|
query.IsLiked = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavorite:
|
||||||
|
query.IsFavorite = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavoriteOrLikes:
|
||||||
|
query.IsFavoriteOrLiked = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFolder:
|
||||||
|
query.IsFolder = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsNotFolder:
|
||||||
|
query.IsFolder = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsPlayed:
|
||||||
|
query.IsPlayed = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsResumable:
|
||||||
|
query.IsResumable = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsUnplayed:
|
||||||
|
query.IsPlayed = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Likes:
|
||||||
|
query.IsLiked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = _libraryManager.GetArtists(query);
|
var result = _libraryManager.GetArtists(query);
|
||||||
|
|
||||||
@@ -358,7 +390,39 @@ public class ArtistsController : BaseJellyfinApiController
|
|||||||
}).Where(i => i is not null).Select(i => i!.Id).ToArray();
|
}).Where(i => i is not null).Select(i => i!.Id).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ApplyFilters(filters);
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case ItemFilter.Dislikes:
|
||||||
|
query.IsLiked = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavorite:
|
||||||
|
query.IsFavorite = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavoriteOrLikes:
|
||||||
|
query.IsFavoriteOrLiked = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFolder:
|
||||||
|
query.IsFolder = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsNotFolder:
|
||||||
|
query.IsFolder = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsPlayed:
|
||||||
|
query.IsPlayed = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsResumable:
|
||||||
|
query.IsResumable = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsUnplayed:
|
||||||
|
query.IsPlayed = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Likes:
|
||||||
|
query.IsLiked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var result = _libraryManager.GetAlbumArtists(query);
|
var result = _libraryManager.GetAlbumArtists(query);
|
||||||
|
|
||||||
|
|||||||
@@ -91,18 +91,18 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[ProducesAudioFile]
|
[ProducesAudioFile]
|
||||||
public async Task<ActionResult> GetAudioStream(
|
public async Task<ActionResult> GetAudioStream(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
[FromQuery] string? @params,
|
[FromQuery] string? @params,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -112,7 +112,7 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -131,8 +131,8 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -255,18 +255,18 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[ProducesAudioFile]
|
[ProducesAudioFile]
|
||||||
public async Task<ActionResult> GetAudioStreamByContainer(
|
public async Task<ActionResult> GetAudioStreamByContainer(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
[FromQuery] string? @params,
|
[FromQuery] string? @params,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -276,7 +276,7 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -295,8 +295,8 @@ public class AudioController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
|
|||||||
@@ -136,13 +136,45 @@ public class ChannelsController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
Limit = limit,
|
Limit = limit,
|
||||||
StartIndex = startIndex,
|
StartIndex = startIndex,
|
||||||
ChannelIds = [channelId],
|
ChannelIds = new[] { channelId },
|
||||||
ParentId = folderId ?? Guid.Empty,
|
ParentId = folderId ?? Guid.Empty,
|
||||||
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
|
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
|
||||||
DtoOptions = new DtoOptions { Fields = fields }
|
DtoOptions = new DtoOptions { Fields = fields }
|
||||||
};
|
};
|
||||||
|
|
||||||
query.ApplyFilters(filters);
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case ItemFilter.IsFolder:
|
||||||
|
query.IsFolder = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsNotFolder:
|
||||||
|
query.IsFolder = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsUnplayed:
|
||||||
|
query.IsPlayed = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsPlayed:
|
||||||
|
query.IsPlayed = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavorite:
|
||||||
|
query.IsFavorite = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsResumable:
|
||||||
|
query.IsResumable = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Likes:
|
||||||
|
query.IsLiked = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Dislikes:
|
||||||
|
query.IsLiked = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavoriteOrLikes:
|
||||||
|
query.IsFavoriteOrLiked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false);
|
return await _channelManager.GetChannelItems(query, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@@ -183,7 +215,39 @@ public class ChannelsController : BaseJellyfinApiController
|
|||||||
DtoOptions = new DtoOptions { Fields = fields }
|
DtoOptions = new DtoOptions { Fields = fields }
|
||||||
};
|
};
|
||||||
|
|
||||||
query.ApplyFilters(filters);
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case ItemFilter.IsFolder:
|
||||||
|
query.IsFolder = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsNotFolder:
|
||||||
|
query.IsFolder = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsUnplayed:
|
||||||
|
query.IsPlayed = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsPlayed:
|
||||||
|
query.IsPlayed = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavorite:
|
||||||
|
query.IsFavorite = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsResumable:
|
||||||
|
query.IsResumable = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Likes:
|
||||||
|
query.IsLiked = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Dislikes:
|
||||||
|
query.IsLiked = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavoriteOrLikes:
|
||||||
|
query.IsFavoriteOrLiked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false);
|
return await _channelManager.GetLatestChannelItems(query, CancellationToken.None).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,17 +191,9 @@ public class DisplayPreferencesController : BaseJellyfinApiController
|
|||||||
|
|
||||||
foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
|
foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
var viewType = displayPreferences.CustomPrefs[key];
|
if (!Enum.TryParse<ViewType>(displayPreferences.CustomPrefs[key], true, out _))
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(viewType))
|
|
||||||
{
|
{
|
||||||
displayPreferences.CustomPrefs.Remove(key);
|
_logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Enum.TryParse<ViewType>(viewType, true, out _))
|
|
||||||
{
|
|
||||||
_logger.LogError("Invalid ViewType: {LandingScreenOption}", viewType);
|
|
||||||
displayPreferences.CustomPrefs.Remove(key);
|
displayPreferences.CustomPrefs.Remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,18 +166,18 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[ProducesPlaylistFile]
|
[ProducesPlaylistFile]
|
||||||
public async Task<ActionResult> GetLiveHlsStream(
|
public async Task<ActionResult> GetLiveHlsStream(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
[FromQuery] string? @params,
|
[FromQuery] string? @params,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -187,7 +187,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -206,8 +206,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -412,12 +412,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery, Required] string mediaSourceId,
|
[FromQuery, Required] string mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -427,7 +427,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -448,8 +448,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -585,12 +585,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery, Required] string mediaSourceId,
|
[FromQuery, Required] string mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -601,7 +601,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -620,8 +620,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -752,12 +752,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -767,7 +767,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -788,8 +788,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -921,12 +921,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -937,7 +937,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -956,8 +956,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -1091,7 +1091,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromRoute, Required] string playlistId,
|
[FromRoute, Required] string playlistId,
|
||||||
[FromRoute, Required] int segmentId,
|
[FromRoute, Required] int segmentId,
|
||||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||||
[FromQuery, Required] long runtimeTicks,
|
[FromQuery, Required] long runtimeTicks,
|
||||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
@@ -1099,12 +1099,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -1114,7 +1114,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -1135,8 +1135,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -1273,7 +1273,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromRoute, Required] string playlistId,
|
[FromRoute, Required] string playlistId,
|
||||||
[FromRoute, Required] int segmentId,
|
[FromRoute, Required] int segmentId,
|
||||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||||
[FromQuery, Required] long runtimeTicks,
|
[FromQuery, Required] long runtimeTicks,
|
||||||
[FromQuery, Required] long actualSegmentLengthTicks,
|
[FromQuery, Required] long actualSegmentLengthTicks,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
@@ -1281,12 +1281,12 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -1297,7 +1297,7 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -1316,8 +1316,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -1403,8 +1403,8 @@ public class DynamicHlsController : BaseJellyfinApiController
|
|||||||
double fps = state.TargetFramerate ?? 0.0f;
|
double fps = state.TargetFramerate ?? 0.0f;
|
||||||
int segmentLength = state.SegmentLength * 1000;
|
int segmentLength = state.SegmentLength * 1000;
|
||||||
|
|
||||||
// If video is transcoded and framerate is fractional (i.e. 23.976), we need to slightly adjust segment length
|
// If framerate is fractional (i.e. 23.976), we need to slightly adjust segment length
|
||||||
if (!EncodingHelper.IsCopyCodec(state.OutputVideoCodec) && Math.Abs(fps - Math.Floor(fps + 0.001f)) > 0.001)
|
if (Math.Abs(fps - Math.Floor(fps + 0.001f)) > 0.001)
|
||||||
{
|
{
|
||||||
double nearestIntFramerate = Math.Ceiling(fps);
|
double nearestIntFramerate = Math.Ceiling(fps);
|
||||||
segmentLength = (int)Math.Ceiling(segmentLength * (nearestIntFramerate / fps));
|
segmentLength = (int)Math.Ceiling(segmentLength * (nearestIntFramerate / fps));
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
item.IndexNumber = request.IndexNumber;
|
item.IndexNumber = request.IndexNumber;
|
||||||
item.ParentIndexNumber = request.ParentIndexNumber;
|
item.ParentIndexNumber = request.ParentIndexNumber;
|
||||||
item.Overview = request.Overview;
|
item.Overview = request.Overview;
|
||||||
item.Genres = request.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
item.Genres = request.Genres;
|
||||||
|
|
||||||
if (item is Episode episode)
|
if (item is Episode episode)
|
||||||
{
|
{
|
||||||
@@ -270,7 +270,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
|
|
||||||
if (request.Studios is not null)
|
if (request.Studios is not null)
|
||||||
{
|
{
|
||||||
item.Studios = Array.ConvertAll(request.Studios, x => x.Name).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
item.Studios = Array.ConvertAll(request.Studios, x => x.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.DateCreated.HasValue)
|
if (request.DateCreated.HasValue)
|
||||||
@@ -287,7 +287,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
item.CustomRating = request.CustomRating;
|
item.CustomRating = request.CustomRating;
|
||||||
|
|
||||||
var currentTags = item.Tags;
|
var currentTags = item.Tags;
|
||||||
var newTags = request.Tags.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
var newTags = request.Tags;
|
||||||
var removedTags = currentTags.Except(newTags).ToList();
|
var removedTags = currentTags.Except(newTags).ToList();
|
||||||
var addedTags = newTags.Except(currentTags).ToList();
|
var addedTags = newTags.Except(currentTags).ToList();
|
||||||
item.Tags = newTags;
|
item.Tags = newTags;
|
||||||
@@ -373,7 +373,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
|
|
||||||
if (request.ProductionLocations is not null)
|
if (request.ProductionLocations is not null)
|
||||||
{
|
{
|
||||||
item.ProductionLocations = request.ProductionLocations.Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
item.ProductionLocations = request.ProductionLocations;
|
||||||
}
|
}
|
||||||
|
|
||||||
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
|
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
|
||||||
@@ -421,7 +421,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
if (item is IHasAlbumArtist hasAlbumArtists)
|
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||||
{
|
{
|
||||||
hasAlbumArtists.AlbumArtists = Array.ConvertAll(request.AlbumArtists, i => i.Name.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
hasAlbumArtists.AlbumArtists = Array.ConvertAll(request.AlbumArtists, i => i.Name.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +429,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
{
|
{
|
||||||
if (item is IHasArtist hasArtists)
|
if (item is IHasArtist hasArtists)
|
||||||
{
|
{
|
||||||
hasArtists.Artists = Array.ConvertAll(request.ArtistItems, i => i.Name.Trim()).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
|
hasArtists.Artists = Array.ConvertAll(request.ArtistItems, i => i.Name.Trim());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using Jellyfin.Extensions;
|
|||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Entities.Movies;
|
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Session;
|
using MediaBrowser.Controller.Session;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
@@ -271,17 +270,15 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
var dtoOptions = new DtoOptions { Fields = fields }
|
var dtoOptions = new DtoOptions { Fields = fields }
|
||||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||||
|
|
||||||
var item = _libraryManager.GetParentItem(parentId, userId);
|
|
||||||
QueryResult<BaseItem> result;
|
|
||||||
|
|
||||||
if (includeItemTypes.Length == 1
|
if (includeItemTypes.Length == 1
|
||||||
&& includeItemTypes[0] == BaseItemKind.BoxSet
|
&& includeItemTypes[0] == BaseItemKind.BoxSet)
|
||||||
&& item is not BoxSet)
|
|
||||||
{
|
{
|
||||||
parentId = null;
|
parentId = null;
|
||||||
item = _libraryManager.GetUserRootFolder();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var item = _libraryManager.GetParentItem(parentId, userId);
|
||||||
|
QueryResult<BaseItem> result;
|
||||||
|
|
||||||
if (item is not Folder folder)
|
if (item is not Folder folder)
|
||||||
{
|
{
|
||||||
folder = _libraryManager.GetUserRootFolder();
|
folder = _libraryManager.GetUserRootFolder();
|
||||||
@@ -389,7 +386,39 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
query.CollapseBoxSetItems = false;
|
query.CollapseBoxSetItems = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
query.ApplyFilters(filters);
|
foreach (var filter in filters)
|
||||||
|
{
|
||||||
|
switch (filter)
|
||||||
|
{
|
||||||
|
case ItemFilter.Dislikes:
|
||||||
|
query.IsLiked = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavorite:
|
||||||
|
query.IsFavorite = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFavoriteOrLikes:
|
||||||
|
query.IsFavoriteOrLiked = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsFolder:
|
||||||
|
query.IsFolder = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsNotFolder:
|
||||||
|
query.IsFolder = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsPlayed:
|
||||||
|
query.IsPlayed = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsResumable:
|
||||||
|
query.IsResumable = true;
|
||||||
|
break;
|
||||||
|
case ItemFilter.IsUnplayed:
|
||||||
|
query.IsPlayed = false;
|
||||||
|
break;
|
||||||
|
case ItemFilter.Likes:
|
||||||
|
query.IsLiked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Filter by Series Status
|
// Filter by Series Status
|
||||||
if (seriesStatus.Length != 0)
|
if (seriesStatus.Length != 0)
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpPost("Tuners/{tunerId}/Reset")]
|
[HttpPost("Tuners/{tunerId}/Reset")]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
|
public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
|
||||||
{
|
{
|
||||||
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
|
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
|
||||||
@@ -976,7 +976,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="200">Created tuner host returned.</response>
|
/// <response code="200">Created tuner host returned.</response>
|
||||||
/// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
|
/// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
|
||||||
[HttpPost("TunerHosts")]
|
[HttpPost("TunerHosts")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
|
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
|
||||||
=> await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
=> await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||||
@@ -988,7 +988,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="204">Tuner host deleted.</response>
|
/// <response code="204">Tuner host deleted.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpDelete("TunerHosts")]
|
[HttpDelete("TunerHosts")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public ActionResult DeleteTunerHost([FromQuery] string? id)
|
public ActionResult DeleteTunerHost([FromQuery] string? id)
|
||||||
{
|
{
|
||||||
@@ -1021,7 +1021,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="200">Created listings provider returned.</response>
|
/// <response code="200">Created listings provider returned.</response>
|
||||||
/// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
|
/// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
|
||||||
[HttpPost("ListingProviders")]
|
[HttpPost("ListingProviders")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
|
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
|
||||||
public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
|
public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
|
||||||
@@ -1047,7 +1047,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="204">Listing provider deleted.</response>
|
/// <response code="204">Listing provider deleted.</response>
|
||||||
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
/// <returns>A <see cref="NoContentResult"/>.</returns>
|
||||||
[HttpDelete("ListingProviders")]
|
[HttpDelete("ListingProviders")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||||
public ActionResult DeleteListingProvider([FromQuery] string? id)
|
public ActionResult DeleteListingProvider([FromQuery] string? id)
|
||||||
{
|
{
|
||||||
@@ -1080,7 +1080,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="200">Available countries returned.</response>
|
/// <response code="200">Available countries returned.</response>
|
||||||
/// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
|
/// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
|
||||||
[HttpGet("ListingProviders/SchedulesDirect/Countries")]
|
[HttpGet("ListingProviders/SchedulesDirect/Countries")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesFile(MediaTypeNames.Application.Json)]
|
[ProducesFile(MediaTypeNames.Application.Json)]
|
||||||
public async Task<ActionResult> GetSchedulesDirectCountries()
|
public async Task<ActionResult> GetSchedulesDirectCountries()
|
||||||
@@ -1101,7 +1101,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="200">Channel mapping options returned.</response>
|
/// <response code="200">Channel mapping options returned.</response>
|
||||||
/// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
|
||||||
[HttpGet("ChannelMappingOptions")]
|
[HttpGet("ChannelMappingOptions")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId)
|
public Task<ChannelMappingOptionsDto> GetChannelMappingOptions([FromQuery] string? providerId)
|
||||||
=> _listingsManager.GetChannelMappingOptions(providerId);
|
=> _listingsManager.GetChannelMappingOptions(providerId);
|
||||||
@@ -1113,7 +1113,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <response code="200">Created channel mapping returned.</response>
|
/// <response code="200">Created channel mapping returned.</response>
|
||||||
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
|
||||||
[HttpPost("ChannelMappings")]
|
[HttpPost("ChannelMappings")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto)
|
public Task<TunerChannelMapping> SetChannelMapping([FromBody, Required] SetChannelMappingDto dto)
|
||||||
=> _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId);
|
=> _listingsManager.SetChannelMapping(dto.ProviderId, dto.TunerChannelId, dto.ProviderChannelId);
|
||||||
@@ -1137,7 +1137,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
/// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
|
||||||
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
|
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
|
||||||
[HttpGet("Tuners/Discover")]
|
[HttpGet("Tuners/Discover")]
|
||||||
[Authorize(Policy = Policies.RequiresElevation)]
|
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||||
=> _tunerHostManager.DiscoverTuners(newDevicesOnly);
|
=> _tunerHostManager.DiscoverTuners(newDevicesOnly);
|
||||||
@@ -1185,7 +1185,7 @@ public class LiveTvController : BaseJellyfinApiController
|
|||||||
[ProducesVideoFile]
|
[ProducesVideoFile]
|
||||||
public ActionResult GetLiveStreamFile(
|
public ActionResult GetLiveStreamFile(
|
||||||
[FromRoute, Required] string streamId,
|
[FromRoute, Required] string streamId,
|
||||||
[FromRoute, Required][RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container)
|
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container)
|
||||||
{
|
{
|
||||||
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
|
var liveStreamInfo = _mediaSourceManager.GetLiveStreamInfoByUniqueId(streamId);
|
||||||
if (liveStreamInfo is null)
|
if (liveStreamInfo is null)
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all persons.
|
/// Gets all persons.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="startIndex">Optional. All items with a lower index will be dropped from the response.</param>
|
|
||||||
/// <param name="limit">Optional. The maximum number of records to return.</param>
|
/// <param name="limit">Optional. The maximum number of records to return.</param>
|
||||||
/// <param name="searchTerm">The search term.</param>
|
/// <param name="searchTerm">The search term.</param>
|
||||||
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
|
/// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
|
||||||
@@ -58,7 +57,6 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
|
/// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
|
||||||
/// <param name="excludePersonTypes">Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited.</param>
|
/// <param name="excludePersonTypes">Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited.</param>
|
||||||
/// <param name="personTypes">Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited.</param>
|
/// <param name="personTypes">Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited.</param>
|
||||||
/// <param name="parentId">Optional. Specify this to localize the search to a specific library. Omit to use the root.</param>
|
|
||||||
/// <param name="appearsInItemId">Optional. If specified, person results will be filtered on items related to said persons.</param>
|
/// <param name="appearsInItemId">Optional. If specified, person results will be filtered on items related to said persons.</param>
|
||||||
/// <param name="userId">User id.</param>
|
/// <param name="userId">User id.</param>
|
||||||
/// <param name="enableImages">Optional, include image information in output.</param>
|
/// <param name="enableImages">Optional, include image information in output.</param>
|
||||||
@@ -67,7 +65,6 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<QueryResult<BaseItemDto>> GetPersons(
|
public ActionResult<QueryResult<BaseItemDto>> GetPersons(
|
||||||
[FromQuery] int? startIndex,
|
|
||||||
[FromQuery] int? limit,
|
[FromQuery] int? limit,
|
||||||
[FromQuery] string? searchTerm,
|
[FromQuery] string? searchTerm,
|
||||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||||
@@ -78,7 +75,6 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes,
|
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes,
|
||||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||||
[FromQuery] Guid? parentId,
|
|
||||||
[FromQuery] Guid? appearsInItemId,
|
[FromQuery] Guid? appearsInItemId,
|
||||||
[FromQuery] Guid? userId,
|
[FromQuery] Guid? userId,
|
||||||
[FromQuery] bool? enableImages = true)
|
[FromQuery] bool? enableImages = true)
|
||||||
@@ -100,8 +96,6 @@ public class PersonsController : BaseJellyfinApiController
|
|||||||
User = user,
|
User = user,
|
||||||
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
|
IsFavorite = !isFavorite.HasValue && isFavoriteInFilters ? true : isFavorite,
|
||||||
AppearsInItemId = appearsInItemId ?? Guid.Empty,
|
AppearsInItemId = appearsInItemId ?? Guid.Empty,
|
||||||
ParentId = parentId,
|
|
||||||
StartIndex = startIndex,
|
|
||||||
Limit = limit ?? 0
|
Limit = limit ?? 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -359,7 +359,6 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="playlistId">The playlist id.</param>
|
/// <param name="playlistId">The playlist id.</param>
|
||||||
/// <param name="ids">Item id, comma delimited.</param>
|
/// <param name="ids">Item id, comma delimited.</param>
|
||||||
/// <param name="position">Optional. 0-based index where to place the items or at the end if <c>null</c>.</param>
|
|
||||||
/// <param name="userId">The userId.</param>
|
/// <param name="userId">The userId.</param>
|
||||||
/// <response code="204">Items added to playlist.</response>
|
/// <response code="204">Items added to playlist.</response>
|
||||||
/// <response code="403">Access forbidden.</response>
|
/// <response code="403">Access forbidden.</response>
|
||||||
@@ -372,7 +371,6 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
public async Task<ActionResult> AddItemToPlaylist(
|
public async Task<ActionResult> AddItemToPlaylist(
|
||||||
[FromRoute, Required] Guid playlistId,
|
[FromRoute, Required] Guid playlistId,
|
||||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
||||||
[FromQuery] int? position,
|
|
||||||
[FromQuery] Guid? userId)
|
[FromQuery] Guid? userId)
|
||||||
{
|
{
|
||||||
userId = RequestHelpers.GetUserId(User, userId);
|
userId = RequestHelpers.GetUserId(User, userId);
|
||||||
@@ -390,7 +388,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
|||||||
return Forbid();
|
return Forbid();
|
||||||
}
|
}
|
||||||
|
|
||||||
await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, position, userId.Value).ConfigureAwait(false);
|
await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false);
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ public class QuickConnectController : BaseJellyfinApiController
|
|||||||
/// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns>
|
/// <returns>A <see cref="QuickConnectResult"/> with a secret and code for future use or an error message.</returns>
|
||||||
[HttpPost("Initiate")]
|
[HttpPost("Initiate")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
||||||
public async Task<ActionResult<QuickConnectResult>> InitiateQuickConnect()
|
public async Task<ActionResult<QuickConnectResult>> InitiateQuickConnect()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ public class SyncPlayController : BaseJellyfinApiController
|
|||||||
[FromBody, Required] NewGroupRequestDto requestData)
|
[FromBody, Required] NewGroupRequestDto requestData)
|
||||||
{
|
{
|
||||||
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
|
var currentSession = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
|
||||||
var syncPlayRequest = new NewGroupRequest(requestData.GroupName.Trim());
|
var syncPlayRequest = new NewGroupRequest(requestData.GroupName);
|
||||||
return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None));
|
return Ok(_syncPlayManager.NewGroup(currentSession, syncPlayRequest, CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,13 +101,13 @@ public class UniversalAudioController : BaseJellyfinApiController
|
|||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] Guid? userId,
|
[FromQuery] Guid? userId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] int? transcodingAudioChannels,
|
[FromQuery] int? transcodingAudioChannels,
|
||||||
[FromQuery] int? maxStreamingBitrate,
|
[FromQuery] int? maxStreamingBitrate,
|
||||||
[FromQuery] int? audioBitRate,
|
[FromQuery] int? audioBitRate,
|
||||||
[FromQuery] long? startTimeTicks,
|
[FromQuery] long? startTimeTicks,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? transcodingContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? transcodingContainer,
|
||||||
[FromQuery] MediaStreamProtocol? transcodingProtocol,
|
[FromQuery] MediaStreamProtocol? transcodingProtocol,
|
||||||
[FromQuery] int? maxAudioSampleRate,
|
[FromQuery] int? maxAudioSampleRate,
|
||||||
[FromQuery] int? maxAudioBitDepth,
|
[FromQuery] int? maxAudioBitDepth,
|
||||||
|
|||||||
@@ -313,18 +313,18 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[ProducesVideoFile]
|
[ProducesVideoFile]
|
||||||
public async Task<ActionResult> GetVideoStream(
|
public async Task<ActionResult> GetVideoStream(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? container,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? container,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
[FromQuery] string? @params,
|
[FromQuery] string? @params,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
[FromQuery, ParameterObsolete] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -334,7 +334,7 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -355,8 +355,8 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
@@ -551,18 +551,18 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[ProducesVideoFile]
|
[ProducesVideoFile]
|
||||||
public Task<ActionResult> GetVideoStreamByContainer(
|
public Task<ActionResult> GetVideoStreamByContainer(
|
||||||
[FromRoute, Required] Guid itemId,
|
[FromRoute, Required] Guid itemId,
|
||||||
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string container,
|
[FromRoute, Required] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string container,
|
||||||
[FromQuery] bool? @static,
|
[FromQuery] bool? @static,
|
||||||
[FromQuery] string? @params,
|
[FromQuery] string? @params,
|
||||||
[FromQuery] string? tag,
|
[FromQuery] string? tag,
|
||||||
[FromQuery] string? deviceProfileId,
|
[FromQuery] string? deviceProfileId,
|
||||||
[FromQuery] string? playSessionId,
|
[FromQuery] string? playSessionId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? segmentContainer,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? segmentContainer,
|
||||||
[FromQuery] int? segmentLength,
|
[FromQuery] int? segmentLength,
|
||||||
[FromQuery] int? minSegments,
|
[FromQuery] int? minSegments,
|
||||||
[FromQuery] string? mediaSourceId,
|
[FromQuery] string? mediaSourceId,
|
||||||
[FromQuery] string? deviceId,
|
[FromQuery] string? deviceId,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? audioCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? audioCodec,
|
||||||
[FromQuery] bool? enableAutoStreamCopy,
|
[FromQuery] bool? enableAutoStreamCopy,
|
||||||
[FromQuery] bool? allowVideoStreamCopy,
|
[FromQuery] bool? allowVideoStreamCopy,
|
||||||
[FromQuery] bool? allowAudioStreamCopy,
|
[FromQuery] bool? allowAudioStreamCopy,
|
||||||
@@ -572,7 +572,7 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? audioChannels,
|
[FromQuery] int? audioChannels,
|
||||||
[FromQuery] int? maxAudioChannels,
|
[FromQuery] int? maxAudioChannels,
|
||||||
[FromQuery] string? profile,
|
[FromQuery] string? profile,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegexStr)] string? level,
|
[FromQuery] [RegularExpression(EncodingHelper.LevelValidationRegex)] string? level,
|
||||||
[FromQuery] float? framerate,
|
[FromQuery] float? framerate,
|
||||||
[FromQuery] float? maxFramerate,
|
[FromQuery] float? maxFramerate,
|
||||||
[FromQuery] bool? copyTimestamps,
|
[FromQuery] bool? copyTimestamps,
|
||||||
@@ -593,8 +593,8 @@ public class VideosController : BaseJellyfinApiController
|
|||||||
[FromQuery] int? cpuCoreLimit,
|
[FromQuery] int? cpuCoreLimit,
|
||||||
[FromQuery] string? liveStreamId,
|
[FromQuery] string? liveStreamId,
|
||||||
[FromQuery] bool? enableMpegtsM2TsMode,
|
[FromQuery] bool? enableMpegtsM2TsMode,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? videoCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? videoCodec,
|
||||||
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegexStr)] string? subtitleCodec,
|
[FromQuery] [RegularExpression(EncodingHelper.ContainerValidationRegex)] string? subtitleCodec,
|
||||||
[FromQuery] string? transcodeReasons,
|
[FromQuery] string? transcodeReasons,
|
||||||
[FromQuery] int? audioStreamIndex,
|
[FromQuery] int? audioStreamIndex,
|
||||||
[FromQuery] int? videoStreamIndex,
|
[FromQuery] int? videoStreamIndex,
|
||||||
|
|||||||
@@ -209,25 +209,6 @@ public class DynamicHlsHelper
|
|||||||
AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
|
AddSubtitles(state, subtitleStreams, builder, _httpContextAccessor.HttpContext.User);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For DoVi profiles without a compatible base layer (P5 HEVC, P10/bl0 AV1),
|
|
||||||
// add a spec-compliant dvh1/dav1 variant before the hvc1 hack variant.
|
|
||||||
// SUPPLEMENTAL-CODECS cannot be used for these profiles (no compatible BL to supplement).
|
|
||||||
// The DoVi variant is listed first so spec-compliant clients (Apple TV, webOS 24+)
|
|
||||||
// select it over the fallback when both have identical BANDWIDTH.
|
|
||||||
// Only emit for clients that explicitly declared DOVI support to avoid breaking
|
|
||||||
// non-compliant players that don't recognize dvh1/dav1 CODECS strings.
|
|
||||||
if (state.VideoStream is not null
|
|
||||||
&& state.VideoRequest is not null
|
|
||||||
&& EncodingHelper.IsCopyCodec(state.OutputVideoCodec)
|
|
||||||
&& state.VideoStream.VideoRangeType == VideoRangeType.DOVI
|
|
||||||
&& state.VideoStream.DvProfile.HasValue
|
|
||||||
&& state.VideoStream.DvLevel.HasValue
|
|
||||||
&& state.GetRequestedRangeTypes(state.VideoStream.Codec)
|
|
||||||
.Contains(VideoRangeType.DOVI.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
AppendDoviPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
var basicPlaylist = AppendPlaylist(builder, state, playlistUrl, totalBitrate, subtitleGroup);
|
||||||
|
|
||||||
if (state.VideoStream is not null && state.VideoRequest is not null)
|
if (state.VideoStream is not null && state.VideoRequest is not null)
|
||||||
@@ -374,65 +355,6 @@ public class DynamicHlsHelper
|
|||||||
return playlistBuilder;
|
return playlistBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Appends a Dolby Vision variant with dvh1/dav1 CODECS for profiles without a compatible
|
|
||||||
/// base layer (P5 HEVC, P10/bl0 AV1). This enables spec-compliant HLS clients to detect
|
|
||||||
/// DoVi from the manifest rather than relying on init segment inspection.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="builder">StringBuilder for the master playlist.</param>
|
|
||||||
/// <param name="state">StreamState of the current stream.</param>
|
|
||||||
/// <param name="url">Playlist URL for this variant.</param>
|
|
||||||
/// <param name="bitrate">Bitrate for the BANDWIDTH field.</param>
|
|
||||||
/// <param name="subtitleGroup">Subtitle group identifier, or null.</param>
|
|
||||||
private void AppendDoviPlaylist(StringBuilder builder, StreamState state, string url, int bitrate, string? subtitleGroup)
|
|
||||||
{
|
|
||||||
var dvProfile = state.VideoStream.DvProfile;
|
|
||||||
var dvLevel = state.VideoStream.DvLevel;
|
|
||||||
if (dvProfile is null || dvLevel is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var playlistBuilder = new StringBuilder();
|
|
||||||
playlistBuilder.Append("#EXT-X-STREAM-INF:BANDWIDTH=")
|
|
||||||
.Append(bitrate.ToString(CultureInfo.InvariantCulture))
|
|
||||||
.Append(",AVERAGE-BANDWIDTH=")
|
|
||||||
.Append(bitrate.ToString(CultureInfo.InvariantCulture));
|
|
||||||
|
|
||||||
playlistBuilder.Append(",VIDEO-RANGE=PQ");
|
|
||||||
|
|
||||||
var dvCodec = HlsCodecStringHelpers.GetDoviString(dvProfile.Value, dvLevel.Value, state.ActualOutputVideoCodec);
|
|
||||||
|
|
||||||
string audioCodecs = string.Empty;
|
|
||||||
if (!string.IsNullOrEmpty(state.ActualOutputAudioCodec))
|
|
||||||
{
|
|
||||||
audioCodecs = GetPlaylistAudioCodecs(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
playlistBuilder.Append(",CODECS=\"")
|
|
||||||
.Append(dvCodec);
|
|
||||||
if (!string.IsNullOrEmpty(audioCodecs))
|
|
||||||
{
|
|
||||||
playlistBuilder.Append(',').Append(audioCodecs);
|
|
||||||
}
|
|
||||||
|
|
||||||
playlistBuilder.Append('"');
|
|
||||||
|
|
||||||
AppendPlaylistResolutionField(playlistBuilder, state);
|
|
||||||
AppendPlaylistFramerateField(playlistBuilder, state);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(subtitleGroup))
|
|
||||||
{
|
|
||||||
playlistBuilder.Append(",SUBTITLES=\"")
|
|
||||||
.Append(subtitleGroup)
|
|
||||||
.Append('"');
|
|
||||||
}
|
|
||||||
|
|
||||||
playlistBuilder.AppendLine();
|
|
||||||
playlistBuilder.AppendLine(url);
|
|
||||||
builder.Append(playlistBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a VIDEO-RANGE field containing the range of the output video stream.
|
/// Appends a VIDEO-RANGE field containing the range of the output video stream.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -346,25 +346,4 @@ public static class HlsCodecStringHelpers
|
|||||||
|
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a Dolby Vision codec string for profiles without a compatible base layer.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="dvProfile">Dolby Vision profile number.</param>
|
|
||||||
/// <param name="dvLevel">Dolby Vision level number.</param>
|
|
||||||
/// <param name="codec">Video codec name (e.g. "hevc", "av1") to determine the DoVi FourCC.</param>
|
|
||||||
/// <returns>Dolby Vision codec string.</returns>
|
|
||||||
public static string GetDoviString(int dvProfile, int dvLevel, string codec)
|
|
||||||
{
|
|
||||||
// HEVC DoVi uses dvh1, AV1 DoVi uses dav1 (out-of-band parameter sets, recommended by Apple HLS spec Rule 1.10)
|
|
||||||
var fourCc = string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase) ? "dav1" : "dvh1";
|
|
||||||
StringBuilder result = new StringBuilder(fourCc, 12);
|
|
||||||
|
|
||||||
result.Append('.')
|
|
||||||
.AppendFormat(CultureInfo.InvariantCulture, "{0:D2}", dvProfile)
|
|
||||||
.Append('.')
|
|
||||||
.AppendFormat(CultureInfo.InvariantCulture, "{0:D2}", dvLevel);
|
|
||||||
|
|
||||||
return result.ToString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ using MediaBrowser.Controller.MediaEncoding;
|
|||||||
using MediaBrowser.Controller.Streaming;
|
using MediaBrowser.Controller.Streaming;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Helpers;
|
namespace Jellyfin.Api.Helpers;
|
||||||
@@ -266,7 +268,7 @@ public static class StreamingHelpers
|
|||||||
Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
|
Dictionary<string, string?> streamOptions = new Dictionary<string, string?>();
|
||||||
foreach (var param in queryString)
|
foreach (var param in queryString)
|
||||||
{
|
{
|
||||||
if (param.Key.Length > 0 && char.IsLower(param.Key[0]))
|
if (char.IsLower(param.Key[0]))
|
||||||
{
|
{
|
||||||
// This was probably not parsed initially and should be a StreamOptions
|
// This was probably not parsed initially and should be a StreamOptions
|
||||||
// or the generated URL should correctly serialize it
|
// or the generated URL should correctly serialize it
|
||||||
@@ -420,18 +422,14 @@ public static class StreamingHelpers
|
|||||||
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
if (videoRequest is not null && IsValidCodecName(val))
|
if (videoRequest is not null)
|
||||||
{
|
{
|
||||||
videoRequest.VideoCodec = val;
|
videoRequest.VideoCodec = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
if (IsValidCodecName(val))
|
|
||||||
{
|
|
||||||
request.AudioCodec = val;
|
request.AudioCodec = val;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 6:
|
case 6:
|
||||||
if (videoRequest is not null)
|
if (videoRequest is not null)
|
||||||
@@ -485,7 +483,7 @@ public static class StreamingHelpers
|
|||||||
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
||||||
break;
|
break;
|
||||||
case 15:
|
case 15:
|
||||||
if (videoRequest is not null && EncodingHelper.LevelValidationRegex().IsMatch(val))
|
if (videoRequest is not null)
|
||||||
{
|
{
|
||||||
videoRequest.Level = val;
|
videoRequest.Level = val;
|
||||||
}
|
}
|
||||||
@@ -506,7 +504,7 @@ public static class StreamingHelpers
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 18:
|
case 18:
|
||||||
if (videoRequest is not null && IsValidCodecName(val))
|
if (videoRequest is not null)
|
||||||
{
|
{
|
||||||
videoRequest.Profile = val;
|
videoRequest.Profile = val;
|
||||||
}
|
}
|
||||||
@@ -565,11 +563,7 @@ public static class StreamingHelpers
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 30:
|
case 30:
|
||||||
if (IsValidCodecName(val))
|
|
||||||
{
|
|
||||||
request.SubtitleCodec = val;
|
request.SubtitleCodec = val;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 31:
|
case 31:
|
||||||
if (videoRequest is not null)
|
if (videoRequest is not null)
|
||||||
@@ -592,11 +586,6 @@ public static class StreamingHelpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidCodecName(string val)
|
|
||||||
{
|
|
||||||
return EncodingHelper.ContainerValidationRegex().IsMatch(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses the container into its file extension.
|
/// Parses the container into its file extension.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
namespace Jellyfin.Api.Models.SyncPlayDtos;
|
namespace Jellyfin.Api.Models.SyncPlayDtos;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -19,6 +17,5 @@ public class NewGroupRequestDto
|
|||||||
/// Gets or sets the group name.
|
/// Gets or sets the group name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The name of the new group.</value>
|
/// <value>The name of the new group.</value>
|
||||||
[StringLength(200, ErrorMessage = "Group name must not exceed 200 characters.")]
|
|
||||||
public string GroupName { get; set; }
|
public string GroupName { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ public static class UserEntityExtensions
|
|||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableSyncTranscoding, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableAudioPlaybackTranscoding, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvAccess, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, false));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableLiveTvManagement, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableSharedDeviceControl, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true));
|
entity.Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true));
|
||||||
entity.Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false));
|
entity.Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false));
|
||||||
|
|||||||
@@ -118,21 +118,15 @@ public class BackupService : IBackupService
|
|||||||
throw new NotSupportedException($"The loaded archive '{archivePath}' is made for a newer version of Jellyfin ({manifest.ServerVersion}) and cannot be loaded in this version.");
|
throw new NotSupportedException($"The loaded archive '{archivePath}' is made for a newer version of Jellyfin ({manifest.ServerVersion}) and cannot be loaded in this version.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CopyDirectory(string source, string target, string[]? exclude = null)
|
void CopyDirectory(string source, string target)
|
||||||
{
|
{
|
||||||
var fullSourcePath = NormalizePathSeparator(Path.GetFullPath(source) + Path.DirectorySeparatorChar);
|
var fullSourcePath = NormalizePathSeparator(Path.GetFullPath(source) + Path.DirectorySeparatorChar);
|
||||||
var fullTargetRoot = Path.GetFullPath(target) + Path.DirectorySeparatorChar;
|
var fullTargetRoot = Path.GetFullPath(target) + Path.DirectorySeparatorChar;
|
||||||
var excludePaths = exclude?.Select(e => $"{source}/{e}/").ToArray();
|
|
||||||
foreach (var item in zipArchive.Entries)
|
foreach (var item in zipArchive.Entries)
|
||||||
{
|
{
|
||||||
var sourcePath = NormalizePathSeparator(Path.GetFullPath(item.FullName));
|
var sourcePath = NormalizePathSeparator(Path.GetFullPath(item.FullName));
|
||||||
var targetPath = Path.GetFullPath(Path.Combine(target, Path.GetRelativePath(source, item.FullName)));
|
var targetPath = Path.GetFullPath(Path.Combine(target, Path.GetRelativePath(source, item.FullName)));
|
||||||
|
|
||||||
if (excludePaths is not null && excludePaths.Any(e => item.FullName.StartsWith(e, StringComparison.Ordinal)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sourcePath.StartsWith(fullSourcePath, StringComparison.Ordinal)
|
if (!sourcePath.StartsWith(fullSourcePath, StringComparison.Ordinal)
|
||||||
|| !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal)
|
|| !targetPath.StartsWith(fullTargetRoot, StringComparison.Ordinal)
|
||||||
|| Path.EndsInDirectorySeparator(item.FullName))
|
|| Path.EndsInDirectorySeparator(item.FullName))
|
||||||
@@ -148,10 +142,8 @@ public class BackupService : IBackupService
|
|||||||
}
|
}
|
||||||
|
|
||||||
CopyDirectory("Config", _applicationPaths.ConfigurationDirectoryPath);
|
CopyDirectory("Config", _applicationPaths.ConfigurationDirectoryPath);
|
||||||
CopyDirectory("Data", _applicationPaths.DataPath, exclude: ["metadata", "metadata-default"]);
|
CopyDirectory("Data", _applicationPaths.DataPath);
|
||||||
CopyDirectory("Root", _applicationPaths.RootFolderPath);
|
CopyDirectory("Root", _applicationPaths.RootFolderPath);
|
||||||
CopyDirectory("Data/metadata", _applicationPaths.InternalMetadataPath);
|
|
||||||
CopyDirectory("Data/metadata-default", _applicationPaths.DefaultInternalMetadataPath);
|
|
||||||
|
|
||||||
if (manifest.Options.Database)
|
if (manifest.Options.Database)
|
||||||
{
|
{
|
||||||
@@ -412,15 +404,6 @@ public class BackupService : IBackupService
|
|||||||
if (backupOptions.Metadata)
|
if (backupOptions.Metadata)
|
||||||
{
|
{
|
||||||
CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata"));
|
CopyDirectory(Path.Combine(_applicationPaths.InternalMetadataPath), Path.Combine("Data", "metadata"));
|
||||||
|
|
||||||
// If a custom metadata path is configured, the default location may still contain data.
|
|
||||||
if (!string.Equals(
|
|
||||||
Path.GetFullPath(_applicationPaths.DefaultInternalMetadataPath),
|
|
||||||
Path.GetFullPath(_applicationPaths.InternalMetadataPath),
|
|
||||||
StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
CopyDirectory(Path.Combine(_applicationPaths.DefaultInternalMetadataPath), Path.Combine("Data", "metadata-default"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifestStream = await zipArchive.CreateEntry(ManifestEntryName).OpenAsync().ConfigureAwait(false);
|
var manifestStream = await zipArchive.CreateEntry(ManifestEntryName).OpenAsync().ConfigureAwait(false);
|
||||||
|
|||||||
@@ -683,15 +683,14 @@ public sealed class BaseItemRepository
|
|||||||
.SelectMany(f => f.Values)
|
.SelectMany(f => f.Values)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var types = allListedItemValues.Select(e => e.MagicNumber).Distinct().ToArray();
|
|
||||||
var values = allListedItemValues.Select(e => e.Value).Distinct().ToArray();
|
|
||||||
var allListedItemValuesSet = allListedItemValues.ToHashSet();
|
|
||||||
|
|
||||||
var existingValues = context.ItemValues
|
var existingValues = context.ItemValues
|
||||||
.Where(e => types.Contains(e.Type) && values.Contains(e.Value))
|
.Select(e => new
|
||||||
.AsEnumerable()
|
{
|
||||||
.Where(e => allListedItemValuesSet.Contains((e.Type, e.Value)))
|
item = e,
|
||||||
|
Key = e.Type + "+" + e.Value
|
||||||
|
})
|
||||||
|
.Where(f => allListedItemValues.Select(e => $"{(int)e.MagicNumber}+{e.Value}").Contains(f.Key))
|
||||||
|
.Select(e => e.item)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).Select(f => new ItemValue()
|
var missingItemValues = allListedItemValues.Except(existingValues.Select(f => (MagicNumber: f.Type, f.Value))).Select(f => new ItemValue()
|
||||||
{
|
{
|
||||||
@@ -1051,7 +1050,7 @@ public sealed class BaseItemRepository
|
|||||||
entity.TotalBitrate = dto.TotalBitrate;
|
entity.TotalBitrate = dto.TotalBitrate;
|
||||||
entity.ExternalId = dto.ExternalId;
|
entity.ExternalId = dto.ExternalId;
|
||||||
entity.Size = dto.Size;
|
entity.Size = dto.Size;
|
||||||
entity.Genres = string.Join('|', dto.Genres.Distinct(StringComparer.OrdinalIgnoreCase));
|
entity.Genres = string.Join('|', dto.Genres);
|
||||||
entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
|
entity.DateCreated = dto.DateCreated == DateTime.MinValue ? null : dto.DateCreated;
|
||||||
entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
|
entity.DateModified = dto.DateModified == DateTime.MinValue ? null : dto.DateModified;
|
||||||
entity.ChannelId = dto.ChannelId;
|
entity.ChannelId = dto.ChannelId;
|
||||||
@@ -1078,9 +1077,9 @@ public sealed class BaseItemRepository
|
|||||||
}
|
}
|
||||||
|
|
||||||
entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
|
entity.ExtraIds = dto.ExtraIds is not null ? string.Join('|', dto.ExtraIds) : null;
|
||||||
entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Where(p => !string.IsNullOrWhiteSpace(p)).Distinct(StringComparer.OrdinalIgnoreCase)) : null;
|
entity.ProductionLocations = dto.ProductionLocations is not null ? string.Join('|', dto.ProductionLocations.Where(p => !string.IsNullOrWhiteSpace(p))) : null;
|
||||||
entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios.Distinct(StringComparer.OrdinalIgnoreCase)) : null;
|
entity.Studios = dto.Studios is not null ? string.Join('|', dto.Studios) : null;
|
||||||
entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags.Distinct(StringComparer.OrdinalIgnoreCase)) : null;
|
entity.Tags = dto.Tags is not null ? string.Join('|', dto.Tags) : null;
|
||||||
entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
|
entity.LockedFields = dto.LockedFields is not null ? dto.LockedFields
|
||||||
.Select(e => new BaseItemMetadataField()
|
.Select(e => new BaseItemMetadataField()
|
||||||
{
|
{
|
||||||
@@ -1123,12 +1122,12 @@ public sealed class BaseItemRepository
|
|||||||
|
|
||||||
if (dto is IHasArtist hasArtists)
|
if (dto is IHasArtist hasArtists)
|
||||||
{
|
{
|
||||||
entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists.Distinct(StringComparer.OrdinalIgnoreCase)) : null;
|
entity.Artists = hasArtists.Artists is not null ? string.Join('|', hasArtists.Artists) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto is IHasAlbumArtist hasAlbumArtists)
|
if (dto is IHasAlbumArtist hasAlbumArtists)
|
||||||
{
|
{
|
||||||
entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtists.Distinct(StringComparer.OrdinalIgnoreCase)) : null;
|
entity.AlbumArtists = hasAlbumArtists.AlbumArtists is not null ? string.Join('|', hasAlbumArtists.AlbumArtists) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto is LiveTvProgram program)
|
if (dto is LiveTvProgram program)
|
||||||
|
|||||||
@@ -62,11 +62,7 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
using var context = _dbProvider.CreateDbContext();
|
using var context = _dbProvider.CreateDbContext();
|
||||||
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct();
|
var dbQuery = TranslateQuery(context.Peoples.AsNoTracking(), context, filter).Select(e => e.Name).Distinct();
|
||||||
|
|
||||||
if (filter.StartIndex.HasValue && filter.StartIndex > 0)
|
// dbQuery = dbQuery.OrderBy(e => e.ListOrder);
|
||||||
{
|
|
||||||
dbQuery = dbQuery.Skip(filter.StartIndex.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.Limit > 0)
|
if (filter.Limit > 0)
|
||||||
{
|
{
|
||||||
dbQuery = dbQuery.Take(filter.Limit);
|
dbQuery = dbQuery.Take(filter.Limit);
|
||||||
@@ -78,10 +74,9 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
|
public void UpdatePeople(Guid itemId, IReadOnlyList<PersonInfo> people)
|
||||||
{
|
{
|
||||||
foreach (var person in people)
|
foreach (var item in people.Where(e => e.Role is null))
|
||||||
{
|
{
|
||||||
person.Name = person.Name.Trim();
|
item.Role = string.Empty;
|
||||||
person.Role = person.Role?.Trim() ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// multiple metadata providers can provide the _same_ person
|
// multiple metadata providers can provide the _same_ person
|
||||||
@@ -201,11 +196,6 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
|||||||
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.ItemId)));
|
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.ItemId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filter.ParentId != null)
|
|
||||||
{
|
|
||||||
query = query.Where(e => e.BaseItems!.Any(w => context.AncestorIds.Any(i => i.ParentItemId == filter.ParentId && i.ItemId == w.ItemId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter.AppearsInItemId.IsEmpty())
|
if (!filter.AppearsInItemId.IsEmpty())
|
||||||
{
|
{
|
||||||
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.AppearsInItemId)));
|
query = query.Where(e => e.BaseItems!.Any(w => w.ItemId.Equals(filter.AppearsInItemId)));
|
||||||
|
|||||||
@@ -182,18 +182,6 @@ public class MediaSegmentManager : IMediaSegmentManager
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
|
public async Task DeleteSegmentsAsync(Guid itemId, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
foreach (var provider in _segmentProviders)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await provider.CleanupExtractedData(itemId, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Provider {ProviderName} failed to clean up extracted data for item {ItemId}", provider.Name, itemId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
var db = await _dbProvider.CreateDbContextAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await using (db.ConfigureAwait(false))
|
await using (db.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -399,8 +399,6 @@ public class TrickplayManager : ITrickplayManager
|
|||||||
var workDir = Path.Combine(_appPaths.TempDirectory, "trickplay_" + Guid.NewGuid().ToString("N"));
|
var workDir = Path.Combine(_appPaths.TempDirectory, "trickplay_" + Guid.NewGuid().ToString("N"));
|
||||||
Directory.CreateDirectory(workDir);
|
Directory.CreateDirectory(workDir);
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var trickplayInfo = new TrickplayInfo
|
var trickplayInfo = new TrickplayInfo
|
||||||
{
|
{
|
||||||
Width = width,
|
Width = width,
|
||||||
@@ -460,12 +458,6 @@ public class TrickplayManager : ITrickplayManager
|
|||||||
|
|
||||||
return trickplayInfo;
|
return trickplayInfo;
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
Directory.Delete(workDir, true);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanGenerateTrickplay(Video video, int interval)
|
private bool CanGenerateTrickplay(Video video, int interval)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Jellyfin.Api.Middleware;
|
|||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Extensions
|
namespace Jellyfin.Server.Extensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using Emby.Server.Implementations;
|
using Emby.Server.Implementations;
|
||||||
using Jellyfin.Api.Auth;
|
using Jellyfin.Api.Auth;
|
||||||
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
|
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
|
||||||
@@ -26,6 +26,7 @@ using Jellyfin.Server.Filters;
|
|||||||
using MediaBrowser.Common.Api;
|
using MediaBrowser.Common.Api;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.Authentication;
|
using Microsoft.AspNetCore.Authentication;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
@@ -33,7 +34,9 @@ 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.Extensions.DependencyInjection.Extensions;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Any;
|
||||||
|
using Microsoft.OpenApi.Interfaces;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.Swagger;
|
using Swashbuckle.AspNetCore.Swagger;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||||
@@ -205,7 +208,7 @@ namespace Jellyfin.Server.Extensions
|
|||||||
{
|
{
|
||||||
{
|
{
|
||||||
"x-jellyfin-version",
|
"x-jellyfin-version",
|
||||||
new JsonNodeExtension(JsonValue.Create(version))
|
new OpenApiString(version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -259,7 +262,6 @@ namespace Jellyfin.Server.Extensions
|
|||||||
c.OperationFilter<FileRequestFilter>();
|
c.OperationFilter<FileRequestFilter>();
|
||||||
c.OperationFilter<ParameterObsoleteFilter>();
|
c.OperationFilter<ParameterObsoleteFilter>();
|
||||||
c.DocumentFilter<AdditionalModelFilter>();
|
c.DocumentFilter<AdditionalModelFilter>();
|
||||||
c.DocumentFilter<SecuritySchemeReferenceFixupFilter>();
|
|
||||||
})
|
})
|
||||||
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
||||||
}
|
}
|
||||||
@@ -331,10 +333,10 @@ namespace Jellyfin.Server.Extensions
|
|||||||
options.MapType<Dictionary<ImageType, string>>(() =>
|
options.MapType<Dictionary<ImageType, string>>(() =>
|
||||||
new OpenApiSchema
|
new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
AdditionalProperties = new OpenApiSchema
|
AdditionalProperties = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String
|
Type = "string"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -342,17 +344,18 @@ namespace Jellyfin.Server.Extensions
|
|||||||
options.MapType<Dictionary<string, string?>>(() =>
|
options.MapType<Dictionary<string, string?>>(() =>
|
||||||
new OpenApiSchema
|
new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
AdditionalProperties = new OpenApiSchema
|
AdditionalProperties = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String | JsonSchemaType.Null
|
Type = "string",
|
||||||
|
Nullable = true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
||||||
options.MapType<Version>(() => new OpenApiSchema
|
options.MapType<Version>(() => new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String
|
Type = "string"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ using System.Collections.Generic;
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using Jellyfin.Server.Migrations;
|
using Jellyfin.Server.Migrations;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Net.WebSocketMessages;
|
using MediaBrowser.Controller.Net.WebSocketMessages;
|
||||||
|
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
|
||||||
using MediaBrowser.Model.ApiClient;
|
using MediaBrowser.Model.ApiClient;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using MediaBrowser.Model.SyncPlay;
|
using MediaBrowser.Model.SyncPlay;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Any;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
@@ -24,7 +25,7 @@ namespace Jellyfin.Server.Filters
|
|||||||
public class AdditionalModelFilter : IDocumentFilter
|
public class AdditionalModelFilter : IDocumentFilter
|
||||||
{
|
{
|
||||||
// Array of options that should not be visible in the api spec.
|
// Array of options that should not be visible in the api spec.
|
||||||
private static readonly Type[] _ignoredConfigurations = [typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions)];
|
private static readonly Type[] _ignoredConfigurations = { typeof(MigrationOptions), typeof(MediaBrowser.Model.Branding.BrandingOptions) };
|
||||||
private readonly IServerConfigurationManager _serverConfigurationManager;
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -47,8 +48,8 @@ namespace Jellyfin.Server.Filters
|
|||||||
&& t != typeof(WebSocketMessageInfo))
|
&& t != typeof(WebSocketMessageInfo))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var inboundWebSocketSchemas = new List<IOpenApiSchema>();
|
var inboundWebSocketSchemas = new List<OpenApiSchema>();
|
||||||
var inboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
var inboundWebSocketDiscriminators = new Dictionary<string, string>();
|
||||||
foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))
|
foreach (var type in webSocketTypes.Where(t => typeof(IInboundWebSocketMessage).IsAssignableFrom(t)))
|
||||||
{
|
{
|
||||||
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||||
@@ -59,16 +60,18 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||||
inboundWebSocketSchemas.Add(schema);
|
inboundWebSocketSchemas.Add(schema);
|
||||||
if (schema is OpenApiSchemaReference schemaRef)
|
inboundWebSocketDiscriminators[messageType.ToString()!] = schema.Reference.ReferenceV3;
|
||||||
{
|
|
||||||
inboundWebSocketDiscriminators[messageType.ToString()!] = schemaRef;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var inboundWebSocketMessageSchema = new OpenApiSchema
|
var inboundWebSocketMessageSchema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
Description = "Represents the list of possible inbound websocket types",
|
Description = "Represents the list of possible inbound websocket types",
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Id = nameof(InboundWebSocketMessage),
|
||||||
|
Type = ReferenceType.Schema
|
||||||
|
},
|
||||||
OneOf = inboundWebSocketSchemas,
|
OneOf = inboundWebSocketSchemas,
|
||||||
Discriminator = new OpenApiDiscriminator
|
Discriminator = new OpenApiDiscriminator
|
||||||
{
|
{
|
||||||
@@ -79,8 +82,8 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema);
|
context.SchemaRepository.AddDefinition(nameof(InboundWebSocketMessage), inboundWebSocketMessageSchema);
|
||||||
|
|
||||||
var outboundWebSocketSchemas = new List<IOpenApiSchema>();
|
var outboundWebSocketSchemas = new List<OpenApiSchema>();
|
||||||
var outboundWebSocketDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
var outboundWebSocketDiscriminators = new Dictionary<string, string>();
|
||||||
foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))
|
foreach (var type in webSocketTypes.Where(t => typeof(IOutboundWebSocketMessage).IsAssignableFrom(t)))
|
||||||
{
|
{
|
||||||
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
var messageType = (SessionMessageType?)type.GetProperty(nameof(WebSocketMessage.MessageType))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||||
@@ -91,55 +94,58 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||||
outboundWebSocketSchemas.Add(schema);
|
outboundWebSocketSchemas.Add(schema);
|
||||||
if (schema is OpenApiSchemaReference schemaRef)
|
outboundWebSocketDiscriminators.Add(messageType.ToString()!, schema.Reference.ReferenceV3);
|
||||||
{
|
|
||||||
outboundWebSocketDiscriminators.Add(messageType.ToString()!, schemaRef);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
|
// Add custom "SyncPlayGroupUpdateMessage" schema because Swashbuckle cannot generate it for us
|
||||||
var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
|
var syncPlayGroupUpdateMessageSchema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
Description = "Untyped sync play command.",
|
Description = "Untyped sync play command.",
|
||||||
Properties = new Dictionary<string, IOpenApiSchema>
|
Properties = new Dictionary<string, OpenApiSchema>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
"Data", new OpenApiSchema
|
"Data", new OpenApiSchema
|
||||||
{
|
{
|
||||||
AllOf = new List<IOpenApiSchema>
|
AllOf =
|
||||||
{
|
[
|
||||||
new OpenApiSchemaReference(nameof(GroupUpdate<object>), null, null)
|
new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(GroupUpdate<object>) } }
|
||||||
},
|
],
|
||||||
Description = "Group update data",
|
Description = "Group update data",
|
||||||
|
Nullable = false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ "MessageId", new OpenApiSchema { Type = JsonSchemaType.String, Format = "uuid", Description = "Gets or sets the message id." } },
|
{ "MessageId", new OpenApiSchema { Type = "string", Format = "uuid", Description = "Gets or sets the message id." } },
|
||||||
{
|
{
|
||||||
"MessageType", new OpenApiSchema
|
"MessageType", new OpenApiSchema
|
||||||
{
|
{
|
||||||
Enum = Enum.GetValues<SessionMessageType>().Select(type => (JsonNode)JsonValue.Create(type.ToString())!).ToList(),
|
Enum = Enum.GetValues<SessionMessageType>().Select(type => new OpenApiString(type.ToString())).ToList<IOpenApiAny>(),
|
||||||
AllOf = new List<IOpenApiSchema>
|
AllOf =
|
||||||
{
|
[
|
||||||
new OpenApiSchemaReference(nameof(SessionMessageType), null, null)
|
new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = nameof(SessionMessageType) } }
|
||||||
},
|
],
|
||||||
Description = "The different kinds of messages that are used in the WebSocket api.",
|
Description = "The different kinds of messages that are used in the WebSocket api.",
|
||||||
Default = JsonValue.Create(nameof(SessionMessageType.SyncPlayGroupUpdate)),
|
Default = new OpenApiString(nameof(SessionMessageType.SyncPlayGroupUpdate)),
|
||||||
ReadOnly = true
|
ReadOnly = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AdditionalPropertiesAllowed = false,
|
AdditionalPropertiesAllowed = false,
|
||||||
|
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "SyncPlayGroupUpdateMessage" }
|
||||||
};
|
};
|
||||||
context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
|
context.SchemaRepository.AddDefinition("SyncPlayGroupUpdateMessage", syncPlayGroupUpdateMessageSchema);
|
||||||
var syncPlayRef = new OpenApiSchemaReference("SyncPlayGroupUpdateMessage", null, null);
|
outboundWebSocketSchemas.Add(syncPlayGroupUpdateMessageSchema);
|
||||||
outboundWebSocketSchemas.Add(syncPlayRef);
|
outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayGroupUpdateMessageSchema.Reference.ReferenceV3;
|
||||||
outboundWebSocketDiscriminators[nameof(SessionMessageType.SyncPlayGroupUpdate)] = syncPlayRef;
|
|
||||||
|
|
||||||
var outboundWebSocketMessageSchema = new OpenApiSchema
|
var outboundWebSocketMessageSchema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
Description = "Represents the list of possible outbound websocket types",
|
Description = "Represents the list of possible outbound websocket types",
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Id = nameof(OutboundWebSocketMessage),
|
||||||
|
Type = ReferenceType.Schema
|
||||||
|
},
|
||||||
OneOf = outboundWebSocketSchemas,
|
OneOf = outboundWebSocketSchemas,
|
||||||
Discriminator = new OpenApiDiscriminator
|
Discriminator = new OpenApiDiscriminator
|
||||||
{
|
{
|
||||||
@@ -153,12 +159,17 @@ namespace Jellyfin.Server.Filters
|
|||||||
nameof(WebSocketMessage),
|
nameof(WebSocketMessage),
|
||||||
new OpenApiSchema
|
new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
Description = "Represents the possible websocket types",
|
Description = "Represents the possible websocket types",
|
||||||
OneOf = new List<IOpenApiSchema>
|
Reference = new OpenApiReference
|
||||||
{
|
{
|
||||||
new OpenApiSchemaReference(nameof(InboundWebSocketMessage), null, null),
|
Id = nameof(WebSocketMessage),
|
||||||
new OpenApiSchemaReference(nameof(OutboundWebSocketMessage), null, null)
|
Type = ReferenceType.Schema
|
||||||
|
},
|
||||||
|
OneOf = new[]
|
||||||
|
{
|
||||||
|
inboundWebSocketMessageSchema,
|
||||||
|
outboundWebSocketMessageSchema
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -169,8 +180,8 @@ namespace Jellyfin.Server.Filters
|
|||||||
&& t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
|
&& t.BaseType.GetGenericTypeDefinition() == typeof(GroupUpdate<>))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var groupUpdateSchemas = new List<IOpenApiSchema>();
|
var groupUpdateSchemas = new List<OpenApiSchema>();
|
||||||
var groupUpdateDiscriminators = new Dictionary<string, OpenApiSchemaReference>();
|
var groupUpdateDiscriminators = new Dictionary<string, string>();
|
||||||
foreach (var type in groupUpdateTypes)
|
foreach (var type in groupUpdateTypes)
|
||||||
{
|
{
|
||||||
var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
var groupUpdateType = (GroupUpdateType?)type.GetProperty(nameof(GroupUpdate<object>.Type))?.GetCustomAttribute<DefaultValueAttribute>()?.Value;
|
||||||
@@ -181,16 +192,18 @@ namespace Jellyfin.Server.Filters
|
|||||||
|
|
||||||
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
var schema = context.SchemaGenerator.GenerateSchema(type, context.SchemaRepository);
|
||||||
groupUpdateSchemas.Add(schema);
|
groupUpdateSchemas.Add(schema);
|
||||||
if (schema is OpenApiSchemaReference schemaRef)
|
groupUpdateDiscriminators[groupUpdateType.ToString()!] = schema.Reference.ReferenceV3;
|
||||||
{
|
|
||||||
groupUpdateDiscriminators[groupUpdateType.ToString()!] = schemaRef;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupUpdateSchema = new OpenApiSchema
|
var groupUpdateSchema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Object,
|
Type = "object",
|
||||||
Description = "Represents the list of possible group update types",
|
Description = "Represents the list of possible group update types",
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Id = nameof(GroupUpdate<object>),
|
||||||
|
Type = ReferenceType.Schema
|
||||||
|
},
|
||||||
OneOf = groupUpdateSchemas,
|
OneOf = groupUpdateSchemas,
|
||||||
Discriminator = new OpenApiDiscriminator
|
Discriminator = new OpenApiDiscriminator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
|||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.Swagger;
|
using Swashbuckle.AspNetCore.Swagger;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ internal sealed class CachingOpenApiProvider : ISwaggerProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public OpenApiDocument GetSwagger(string documentName, string host, string basePath)
|
public OpenApiDocument GetSwagger(string documentName, string? host = null, string? basePath = null)
|
||||||
{
|
{
|
||||||
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
|
if (_memoryCache.TryGetValue(CacheKey, out OpenApiDocument? openApiDocument) && openApiDocument is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Jellyfin.Api.Attributes;
|
using Jellyfin.Api.Attributes;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
@@ -28,11 +28,10 @@ namespace Jellyfin.Server.Filters
|
|||||||
{
|
{
|
||||||
Schema = new OpenApiSchema
|
Schema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String,
|
Type = "string",
|
||||||
Format = "binary"
|
Format = "binary"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
body.Content ??= new System.Collections.Generic.Dictionary<string, OpenApiMediaType>();
|
|
||||||
foreach (var contentType in contentTypes)
|
foreach (var contentType in contentTypes)
|
||||||
{
|
{
|
||||||
body.Content.Add(contentType, mediaType);
|
body.Content.Add(contentType, mediaType);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Api.Attributes;
|
using Jellyfin.Api.Attributes;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Filters
|
|||||||
{
|
{
|
||||||
Schema = new OpenApiSchema
|
Schema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String,
|
Type = "string",
|
||||||
Format = "binary"
|
Format = "binary"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -22,11 +22,6 @@ namespace Jellyfin.Server.Filters
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
{
|
{
|
||||||
if (operation.Responses is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
|
foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
|
||||||
{
|
{
|
||||||
if (attribute is ProducesFileAttribute producesFileAttribute)
|
if (attribute is ProducesFileAttribute producesFileAttribute)
|
||||||
@@ -36,7 +31,7 @@ namespace Jellyfin.Server.Filters
|
|||||||
.FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
|
.FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
|
||||||
|
|
||||||
// Operation doesn't have a response.
|
// Operation doesn't have a response.
|
||||||
if (response.Value?.Content is null)
|
if (response.Value is null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
namespace Jellyfin.Server.Filters;
|
||||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
|
|||||||
public class FlagsEnumSchemaFilter : ISchemaFilter
|
public class FlagsEnumSchemaFilter : ISchemaFilter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
|
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||||
{
|
{
|
||||||
var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
|
var type = context.Type.IsEnum ? context.Type : Nullable.GetUnderlyingType(context.Type);
|
||||||
if (type is null || !type.IsEnum)
|
if (type is null || !type.IsEnum)
|
||||||
@@ -29,16 +29,11 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema is not OpenApiSchema concreteSchema)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.MemberInfo is null)
|
if (context.MemberInfo is null)
|
||||||
{
|
{
|
||||||
// Processing the enum definition itself - ensure it's type "string" not "integer"
|
// Processing the enum definition itself - ensure it's type "string" not "integer"
|
||||||
concreteSchema.Type = JsonSchemaType.String;
|
schema.Type = "string";
|
||||||
concreteSchema.Format = null;
|
schema.Format = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -48,11 +43,11 @@ public class FlagsEnumSchemaFilter : ISchemaFilter
|
|||||||
|
|
||||||
// Flags enums should be represented as arrays referencing the enum schema
|
// Flags enums should be represented as arrays referencing the enum schema
|
||||||
// since multiple values can be combined
|
// since multiple values can be combined
|
||||||
concreteSchema.Type = JsonSchemaType.Array;
|
schema.Type = "array";
|
||||||
concreteSchema.Format = null;
|
schema.Format = null;
|
||||||
concreteSchema.Enum = null;
|
schema.Enum = null;
|
||||||
concreteSchema.AllOf = null;
|
schema.AllOf = null;
|
||||||
concreteSchema.Items = enumSchema;
|
schema.Items = enumSchema;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using Jellyfin.Data.Attributes;
|
using Jellyfin.Data.Attributes;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Any;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
namespace Jellyfin.Server.Filters;
|
||||||
@@ -15,7 +15,7 @@ namespace Jellyfin.Server.Filters;
|
|||||||
public class IgnoreEnumSchemaFilter : ISchemaFilter
|
public class IgnoreEnumSchemaFilter : ISchemaFilter
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
|
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||||
{
|
{
|
||||||
if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false))
|
if (context.Type.IsEnum || (Nullable.GetUnderlyingType(context.Type)?.IsEnum ?? false))
|
||||||
{
|
{
|
||||||
@@ -25,23 +25,18 @@ public class IgnoreEnumSchemaFilter : ISchemaFilter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema is not OpenApiSchema concreteSchema)
|
var enumOpenApiStrings = new List<IOpenApiAny>();
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var enumOpenApiNodes = new List<JsonNode>();
|
|
||||||
|
|
||||||
foreach (var enumName in Enum.GetNames(type))
|
foreach (var enumName in Enum.GetNames(type))
|
||||||
{
|
{
|
||||||
var member = type.GetMember(enumName)[0];
|
var member = type.GetMember(enumName)[0];
|
||||||
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
|
if (!member.GetCustomAttributes<OpenApiIgnoreEnumAttribute>().Any())
|
||||||
{
|
{
|
||||||
enumOpenApiNodes.Add(JsonValue.Create(enumName)!);
|
enumOpenApiStrings.Add(new OpenApiString(enumName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
concreteSchema.Enum = enumOpenApiNodes;
|
schema.Enum = enumOpenApiStrings;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Api.Attributes;
|
using Jellyfin.Api.Attributes;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters
|
namespace Jellyfin.Server.Filters
|
||||||
@@ -21,17 +21,11 @@ namespace Jellyfin.Server.Filters
|
|||||||
.OfType<ParameterObsoleteAttribute>()
|
.OfType<ParameterObsoleteAttribute>()
|
||||||
.Any())
|
.Any())
|
||||||
{
|
{
|
||||||
if (operation.Parameters is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var parameter in operation.Parameters)
|
foreach (var parameter in operation.Parameters)
|
||||||
{
|
{
|
||||||
if (parameter is OpenApiParameter concreteParam
|
if (parameter.Name.Equals(parameterDescription.Name, StringComparison.Ordinal))
|
||||||
&& string.Equals(concreteParam.Name, parameterDescription.Name, StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
concreteParam.Deprecated = true;
|
parameter.Deprecated = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
namespace Jellyfin.Server.Filters;
|
||||||
@@ -8,12 +8,12 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
|||||||
{
|
{
|
||||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||||
{
|
{
|
||||||
operation.Responses?.TryAdd(
|
operation.Responses.TryAdd(
|
||||||
"503",
|
"503",
|
||||||
new OpenApiResponse
|
new OpenApiResponse
|
||||||
{
|
{
|
||||||
Description = "The server is currently starting or is temporarily not available.",
|
Description = "The server is currently starting or is temporarily not available.",
|
||||||
Headers = new Dictionary<string, IOpenApiHeader>
|
Headers = new Dictionary<string, OpenApiHeader>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
"Retry-After", new OpenApiHeader
|
"Retry-After", new OpenApiHeader
|
||||||
@@ -23,7 +23,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
|||||||
Description = "A hint for when to retry the operation in full seconds.",
|
Description = "A hint for when to retry the operation in full seconds.",
|
||||||
Schema = new OpenApiSchema
|
Schema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.Integer,
|
Type = "integer",
|
||||||
Format = "int32"
|
Format = "int32"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ internal class RetryOnTemporarilyUnavailableFilter : IOperationFilter
|
|||||||
Description = "A short plain-text reason why the server is not available.",
|
Description = "A short plain-text reason why the server is not available.",
|
||||||
Schema = new OpenApiSchema
|
Schema = new OpenApiSchema
|
||||||
{
|
{
|
||||||
Type = JsonSchemaType.String,
|
Type = "string",
|
||||||
Format = "text"
|
Format = "text"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
|||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
namespace Jellyfin.Server.Filters;
|
||||||
@@ -66,10 +66,17 @@ public class SecurityRequirementsOperationFilter : IOperationFilter
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
operation.Responses?.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
operation.Responses.TryAdd("401", new OpenApiResponse { Description = "Unauthorized" });
|
||||||
operation.Responses?.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
operation.Responses.TryAdd("403", new OpenApiResponse { Description = "Forbidden" });
|
||||||
|
|
||||||
var scheme = new OpenApiSecuritySchemeReference(AuthenticationSchemes.CustomAuthentication, null, null);
|
var scheme = new OpenApiSecurityScheme
|
||||||
|
{
|
||||||
|
Reference = new OpenApiReference
|
||||||
|
{
|
||||||
|
Type = ReferenceType.SecurityScheme,
|
||||||
|
Id = AuthenticationSchemes.CustomAuthentication
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Add DefaultAuthorization scope to any endpoint that has a policy with a requirement that is a subset of DefaultAuthorization.
|
// Add DefaultAuthorization scope to any endpoint that has a policy with a requirement that is a subset of DefaultAuthorization.
|
||||||
if (!requiredScopes.Contains(DefaultAuthPolicy.AsSpan(), StringComparison.Ordinal))
|
if (!requiredScopes.Contains(DefaultAuthPolicy.AsSpan(), StringComparison.Ordinal))
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
using Microsoft.OpenApi;
|
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
|
||||||
|
|
||||||
namespace Jellyfin.Server.Filters;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Document filter that fixes security scheme references after document generation.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// In Microsoft.OpenApi v2, <see cref="OpenApiSecuritySchemeReference"/> requires a resolved
|
|
||||||
/// <c>Target</c> to serialize correctly. References created without a host document (as in
|
|
||||||
/// operation filters) serialize as empty objects. This filter re-creates all security scheme
|
|
||||||
/// references with the document context so they resolve properly during serialization.
|
|
||||||
/// </remarks>
|
|
||||||
internal class SecuritySchemeReferenceFixupFilter : IDocumentFilter
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
|
||||||
{
|
|
||||||
swaggerDoc.RegisterComponents();
|
|
||||||
|
|
||||||
if (swaggerDoc.Paths is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var pathItem in swaggerDoc.Paths.Values)
|
|
||||||
{
|
|
||||||
if (pathItem.Operations is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var operation in pathItem.Operations.Values)
|
|
||||||
{
|
|
||||||
if (operation.Security is null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < operation.Security.Count; i++)
|
|
||||||
{
|
|
||||||
var oldReq = operation.Security[i];
|
|
||||||
var newReq = new OpenApiSecurityRequirement();
|
|
||||||
foreach (var kvp in oldReq)
|
|
||||||
{
|
|
||||||
var fixedRef = new OpenApiSecuritySchemeReference(kvp.Key.Reference.Id!, swaggerDoc);
|
|
||||||
newReq[fixedRef] = kvp.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
operation.Security[i] = newReq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Jellyfin.Server.ServerSetupApp;
|
|
||||||
using MediaBrowser.Controller.Entities;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Jellyfin.Server.Migrations.Routines;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Migration to fix broken library subtitle download languages.
|
|
||||||
/// </summary>
|
|
||||||
[JellyfinMigration("2026-02-06T20:00:00", nameof(FixLibrarySubtitleDownloadLanguages))]
|
|
||||||
internal class FixLibrarySubtitleDownloadLanguages : IAsyncMigrationRoutine
|
|
||||||
{
|
|
||||||
private readonly ILocalizationManager _localizationManager;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="FixLibrarySubtitleDownloadLanguages"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="localizationManager">The Localization manager.</param>
|
|
||||||
/// <param name="startupLogger">The startup logger for Startup UI integration.</param>
|
|
||||||
/// <param name="libraryManager">The Library manager.</param>
|
|
||||||
/// <param name="logger">The logger.</param>
|
|
||||||
public FixLibrarySubtitleDownloadLanguages(
|
|
||||||
ILocalizationManager localizationManager,
|
|
||||||
IStartupLogger<FixLibrarySubtitleDownloadLanguages> startupLogger,
|
|
||||||
ILibraryManager libraryManager,
|
|
||||||
ILogger<FixLibrarySubtitleDownloadLanguages> logger)
|
|
||||||
{
|
|
||||||
_localizationManager = localizationManager;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_logger = startupLogger.With(logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public Task PerformAsync(CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Starting to fix library subtitle download languages.");
|
|
||||||
|
|
||||||
var virtualFolders = _libraryManager.GetVirtualFolders(false);
|
|
||||||
|
|
||||||
foreach (var virtualFolder in virtualFolders)
|
|
||||||
{
|
|
||||||
var options = virtualFolder.LibraryOptions;
|
|
||||||
if (options?.SubtitleDownloadLanguages is null || options.SubtitleDownloadLanguages.Length == 0)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some virtual folders don't have a proper item id.
|
|
||||||
if (!Guid.TryParse(virtualFolder.ItemId, out var folderId))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var collectionFolder = _libraryManager.GetItemById<CollectionFolder>(folderId);
|
|
||||||
if (collectionFolder is null)
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Could not find collection folder for virtual folder '{LibraryName}' with id '{FolderId}'. Skipping.", virtualFolder.Name, folderId);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var fixedLanguages = new List<string>();
|
|
||||||
|
|
||||||
foreach (var language in options.SubtitleDownloadLanguages)
|
|
||||||
{
|
|
||||||
var foundLanguage = _localizationManager.FindLanguageInfo(language)?.ThreeLetterISOLanguageName;
|
|
||||||
if (foundLanguage is not null)
|
|
||||||
{
|
|
||||||
// Converted ISO 639-2/B to T (ger to deu)
|
|
||||||
if (!string.Equals(foundLanguage, language, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Converted '{Language}' to '{ResolvedLanguage}' in library '{LibraryName}'.", language, foundLanguage, virtualFolder.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fixedLanguages.Contains(foundLanguage, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Language '{Language}' already exists for library '{LibraryName}'. Skipping duplicate.", foundLanguage, virtualFolder.Name);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixedLanguages.Add(foundLanguage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Could not resolve language '{Language}' in library '{LibraryName}'. Skipping.", language, virtualFolder.Name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
options.SubtitleDownloadLanguages = [.. fixedLanguages];
|
|
||||||
collectionFolder.UpdateLibraryOptions(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.LogInformation("Library subtitle download languages fixed.");
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -464,16 +464,6 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
|
|
||||||
SqliteConnection.ClearAllPools();
|
SqliteConnection.ClearAllPools();
|
||||||
|
|
||||||
using (var checkpointConnection = new SqliteConnection($"Filename={libraryDbPath}"))
|
|
||||||
{
|
|
||||||
checkpointConnection.Open();
|
|
||||||
using var cmd = checkpointConnection.CreateCommand();
|
|
||||||
cmd.CommandText = "PRAGMA wal_checkpoint(TRUNCATE);";
|
|
||||||
cmd.ExecuteNonQuery();
|
|
||||||
}
|
|
||||||
|
|
||||||
SqliteConnection.ClearAllPools();
|
|
||||||
|
|
||||||
_logger.LogInformation("Move {0} to {1}.", libraryDbPath, libraryDbPath + ".old");
|
_logger.LogInformation("Move {0} to {1}.", libraryDbPath, libraryDbPath + ".old");
|
||||||
File.Move(libraryDbPath, libraryDbPath + ".old", true);
|
File.Move(libraryDbPath, libraryDbPath + ".old", true);
|
||||||
}
|
}
|
||||||
@@ -515,7 +505,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
PlayCount = dto.GetInt32(4),
|
PlayCount = dto.GetInt32(4),
|
||||||
IsFavorite = dto.GetBoolean(5),
|
IsFavorite = dto.GetBoolean(5),
|
||||||
PlaybackPositionTicks = dto.GetInt64(6),
|
PlaybackPositionTicks = dto.GetInt64(6),
|
||||||
LastPlayedDate = dto.IsDBNull(7) ? null : ReadDateTimeFromColumn(dto, 7),
|
LastPlayedDate = dto.IsDBNull(7) ? null : dto.GetDateTime(7),
|
||||||
AudioStreamIndex = dto.IsDBNull(8) ? null : dto.GetInt32(8),
|
AudioStreamIndex = dto.IsDBNull(8) ? null : dto.GetInt32(8),
|
||||||
SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9),
|
SubtitleStreamIndex = dto.IsDBNull(9) ? null : dto.GetInt32(9),
|
||||||
Likes = null,
|
Likes = null,
|
||||||
@@ -524,28 +514,6 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DateTime? ReadDateTimeFromColumn(SqliteDataReader reader, int index)
|
|
||||||
{
|
|
||||||
// Try reading as a formatted date string first (handles ISO-8601 dates).
|
|
||||||
if (reader.TryReadDateTime(index, out var dateTimeResult))
|
|
||||||
{
|
|
||||||
return dateTimeResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some databases have Unix epoch timestamps stored as integers.
|
|
||||||
// SqliteDataReader.GetDateTime interprets integers as Julian dates, which crashes
|
|
||||||
// for Unix epoch values. Handle them explicitly.
|
|
||||||
var rawValue = reader.GetValue(index);
|
|
||||||
if (rawValue is long unixTimestamp
|
|
||||||
&& unixTimestamp > 0
|
|
||||||
&& unixTimestamp <= DateTimeOffset.MaxValue.ToUnixTimeSeconds())
|
|
||||||
{
|
|
||||||
return DateTimeOffset.FromUnixTimeSeconds(unixTimestamp).UtcDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private AncestorId GetAncestorId(SqliteDataReader reader)
|
private AncestorId GetAncestorId(SqliteDataReader reader)
|
||||||
{
|
{
|
||||||
return new AncestorId()
|
return new AncestorId()
|
||||||
@@ -1195,9 +1163,7 @@ internal class MigrateLibraryDb : IDatabaseMigrationRoutine
|
|||||||
Item = null!,
|
Item = null!,
|
||||||
ProviderId = e[0],
|
ProviderId = e[0],
|
||||||
ProviderValue = string.Join('|', e.Skip(1))
|
ProviderValue = string.Join('|', e.Skip(1))
|
||||||
})
|
}).ToArray();
|
||||||
.DistinctBy(e => e.ProviderId)
|
|
||||||
.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reader.TryGetString(index++, out var imageInfos))
|
if (reader.TryGetString(index++, out var imageInfos))
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public sealed class SetupServer : IDisposable
|
|||||||
{
|
{
|
||||||
var knownBindInterfaces = NetworkManager.GetInterfacesCore(_loggerFactory.CreateLogger<SetupServer>(), config.EnableIPv4, config.EnableIPv6);
|
var knownBindInterfaces = NetworkManager.GetInterfacesCore(_loggerFactory.CreateLogger<SetupServer>(), config.EnableIPv4, config.EnableIPv6);
|
||||||
knownBindInterfaces = NetworkManager.FilterBindSettings(config, knownBindInterfaces.ToList(), config.EnableIPv4, config.EnableIPv6);
|
knownBindInterfaces = NetworkManager.FilterBindSettings(config, knownBindInterfaces.ToList(), config.EnableIPv4, config.EnableIPv6);
|
||||||
var bindInterfaces = NetworkManager.GetAllBindInterfaces(_loggerFactory.CreateLogger<NetworkManager>(), false, _configurationManager, knownBindInterfaces, config.EnableIPv4, config.EnableIPv6);
|
var bindInterfaces = NetworkManager.GetAllBindInterfaces(false, _configurationManager, knownBindInterfaces, config.EnableIPv4, config.EnableIPv6);
|
||||||
Extensions.WebHostBuilderExtensions.SetupJellyfinWebServer(
|
Extensions.WebHostBuilderExtensions.SetupJellyfinWebServer(
|
||||||
bindInterfaces,
|
bindInterfaces,
|
||||||
config.InternalHttpPort,
|
config.InternalHttpPort,
|
||||||
|
|||||||
@@ -154,6 +154,11 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||||||
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
|
return "Artist-" + (Name ?? string.Empty).RemoveDiacritics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool GetBlockUnratedValue(User user)
|
||||||
|
{
|
||||||
|
return user.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems).Contains(UnratedItem.Music);
|
||||||
|
}
|
||||||
|
|
||||||
public override UnratedItem GetBlockUnratedType()
|
public override UnratedItem GetBlockUnratedType()
|
||||||
{
|
{
|
||||||
return UnratedItem.Music;
|
return UnratedItem.Music;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Channels;
|
|||||||
using MediaBrowser.Controller.Chapters;
|
using MediaBrowser.Controller.Chapters;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.IO;
|
using MediaBrowser.Controller.IO;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@@ -1171,18 +1172,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
info.Video3DFormat = video.Video3DFormat;
|
info.Video3DFormat = video.Video3DFormat;
|
||||||
info.Timestamp = video.Timestamp;
|
info.Timestamp = video.Timestamp;
|
||||||
|
|
||||||
if (video.IsShortcut && !string.IsNullOrEmpty(video.ShortcutPath))
|
if (video.IsShortcut)
|
||||||
{
|
|
||||||
var shortcutProtocol = MediaSourceManager.GetPathProtocol(video.ShortcutPath);
|
|
||||||
|
|
||||||
// Only allow remote shortcut paths — local file paths in .strm files
|
|
||||||
// could be used to read arbitrary files from the server.
|
|
||||||
if (shortcutProtocol != MediaProtocol.File)
|
|
||||||
{
|
{
|
||||||
info.IsRemote = true;
|
info.IsRemote = true;
|
||||||
info.Path = video.ShortcutPath;
|
info.Path = video.ShortcutPath;
|
||||||
info.Protocol = shortcutProtocol;
|
info.Protocol = MediaSourceManager.GetPathProtocol(info.Path);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(info.Container))
|
if (string.IsNullOrEmpty(info.Container))
|
||||||
@@ -1607,10 +1601,11 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(rating))
|
if (string.IsNullOrEmpty(rating))
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug("{0} has no parental rating set.", Name);
|
||||||
return !GetBlockUnratedValue(user);
|
return !GetBlockUnratedValue(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ratingScore = LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode());
|
var ratingScore = LocalizationManager.GetRatingScore(rating);
|
||||||
|
|
||||||
// Could not determine rating level
|
// Could not determine rating level
|
||||||
if (ratingScore is null)
|
if (ratingScore is null)
|
||||||
@@ -1652,7 +1647,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode());
|
return LocalizationManager.GetRatingScore(rating);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<string> GetInheritedTags()
|
public List<string> GetInheritedTags()
|
||||||
@@ -2134,6 +2129,17 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Music albums usually don't have dedicated backdrops, so return one from the artist instead
|
||||||
|
if (GetType() == typeof(MusicAlbum) && imageType == ImageType.Backdrop)
|
||||||
|
{
|
||||||
|
var artist = FindParent<MusicArtist>();
|
||||||
|
|
||||||
|
if (artist is not null)
|
||||||
|
{
|
||||||
|
return artist.GetImages(imageType).ElementAtOrDefault(imageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return GetImages(imageType)
|
return GetImages(imageType)
|
||||||
.ElementAtOrDefault(imageIndex);
|
.ElementAtOrDefault(imageIndex);
|
||||||
}
|
}
|
||||||
@@ -2615,7 +2621,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
.Select(i => i.OfficialRating)
|
.Select(i => i.OfficialRating)
|
||||||
.Where(i => !string.IsNullOrEmpty(i))
|
.Where(i => !string.IsNullOrEmpty(i))
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.Select(rating => (rating, LocalizationManager.GetRatingScore(rating, GetPreferredMetadataCountryCode())))
|
.Select(rating => (rating, LocalizationManager.GetRatingScore(rating)))
|
||||||
.OrderBy(i => i.Item2 is null ? 1001 : i.Item2.Score)
|
.OrderBy(i => i.Item2 is null ? 1001 : i.Item2.Score)
|
||||||
.ThenBy(i => i.Item2 is null ? 1001 : i.Item2.SubScore)
|
.ThenBy(i => i.Item2 is null ? 1001 : i.Item2.SubScore)
|
||||||
.Select(i => i.rating);
|
.Select(i => i.rating);
|
||||||
|
|||||||
@@ -452,7 +452,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
// That's all the new and changed ones - now see if any have been removed and need cleanup
|
// That's all the new and changed ones - now see if any have been removed and need cleanup
|
||||||
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
|
var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
|
||||||
var shouldRemove = !IsRoot || allowRemoveRoot;
|
var shouldRemove = !IsRoot || allowRemoveRoot;
|
||||||
var actuallyRemoved = new List<BaseItem>();
|
|
||||||
// If it's an AggregateFolder, don't remove
|
// If it's an AggregateFolder, don't remove
|
||||||
if (shouldRemove && itemsRemoved.Count > 0)
|
if (shouldRemove && itemsRemoved.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -468,7 +467,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
Logger.LogDebug("Removed item: {Path}", item.Path);
|
Logger.LogDebug("Removed item: {Path}", item.Path);
|
||||||
|
|
||||||
actuallyRemoved.Add(item);
|
|
||||||
item.SetParent(null);
|
item.SetParent(null);
|
||||||
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
|
||||||
}
|
}
|
||||||
@@ -479,20 +477,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
{
|
{
|
||||||
LibraryManager.CreateItems(newItems, this, cancellationToken);
|
LibraryManager.CreateItems(newItems, this, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// After removing items, reattach any detached user data to remaining children
|
|
||||||
// that share the same user data keys (eg. same episode replaced with a new file).
|
|
||||||
if (actuallyRemoved.Count > 0)
|
|
||||||
{
|
|
||||||
var removedKeys = actuallyRemoved.SelectMany(i => i.GetUserDataKeys()).ToHashSet();
|
|
||||||
foreach (var child in validChildren)
|
|
||||||
{
|
|
||||||
if (child.GetUserDataKeys().Any(removedKeys.Contains))
|
|
||||||
{
|
|
||||||
await child.ReattachUserDataAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ using Jellyfin.Database.Implementations.Entities;
|
|||||||
using Jellyfin.Database.Implementations.Enums;
|
using Jellyfin.Database.Implementations.Enums;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
@@ -389,75 +388,5 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
User = user;
|
User = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyFilters(ItemFilter[] filters)
|
|
||||||
{
|
|
||||||
static void ThrowConflictingFilters()
|
|
||||||
=> throw new ArgumentException("Conflicting filters", nameof(filters));
|
|
||||||
|
|
||||||
foreach (var filter in filters)
|
|
||||||
{
|
|
||||||
switch (filter)
|
|
||||||
{
|
|
||||||
case ItemFilter.IsFolder:
|
|
||||||
if (filters.Contains(ItemFilter.IsNotFolder))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsFolder = true;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsNotFolder:
|
|
||||||
if (filters.Contains(ItemFilter.IsFolder))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsFolder = false;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsUnplayed:
|
|
||||||
if (filters.Contains(ItemFilter.IsPlayed))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsPlayed = false;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsPlayed:
|
|
||||||
if (filters.Contains(ItemFilter.IsUnplayed))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsPlayed = true;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsFavorite:
|
|
||||||
IsFavorite = true;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsResumable:
|
|
||||||
IsResumable = true;
|
|
||||||
break;
|
|
||||||
case ItemFilter.Likes:
|
|
||||||
if (filters.Contains(ItemFilter.Dislikes))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsLiked = true;
|
|
||||||
break;
|
|
||||||
case ItemFilter.Dislikes:
|
|
||||||
if (filters.Contains(ItemFilter.Likes))
|
|
||||||
{
|
|
||||||
ThrowConflictingFilters();
|
|
||||||
}
|
|
||||||
|
|
||||||
IsLiked = false;
|
|
||||||
break;
|
|
||||||
case ItemFilter.IsFavoriteOrLikes:
|
|
||||||
IsFavoriteOrLiked = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
ExcludePersonTypes = excludePersonTypes;
|
ExcludePersonTypes = excludePersonTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? StartIndex { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the maximum number of items the query should return.
|
/// Gets or sets the maximum number of items the query should return.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -30,8 +28,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
public Guid ItemId { get; set; }
|
public Guid ItemId { get; set; }
|
||||||
|
|
||||||
public Guid? ParentId { get; set; }
|
|
||||||
|
|
||||||
public IReadOnlyList<string> PersonTypes { get; }
|
public IReadOnlyList<string> PersonTypes { get; }
|
||||||
|
|
||||||
public IReadOnlyList<string> ExcludePersonTypes { get; }
|
public IReadOnlyList<string> ExcludePersonTypes { get; }
|
||||||
|
|||||||
@@ -201,17 +201,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||||||
|
|
||||||
public List<BaseItem> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes, DtoOptions options, bool shouldIncludeMissingEpisodes)
|
public List<BaseItem> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes, DtoOptions options, bool shouldIncludeMissingEpisodes)
|
||||||
{
|
{
|
||||||
if (series is null)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes);
|
return series.GetSeasonEpisodes(this, user, allSeriesEpisodes, options, shouldIncludeMissingEpisodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BaseItem> GetEpisodes()
|
public List<BaseItem> GetEpisodes()
|
||||||
{
|
{
|
||||||
return GetEpisodes(Series, null, null, new DtoOptions(true), true);
|
return Series.GetSeasonEpisodes(this, null, null, new DtoOptions(true), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
|
public override List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||||||
|
|
||||||
if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
|
if (!currentSeasonNumber.HasValue && !seasonNumber.HasValue && parentSeason.LocationType == LocationType.Virtual)
|
||||||
{
|
{
|
||||||
return episodeItem.Season is null or { LocationType: LocationType.Virtual };
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var season = episodeItem.Season;
|
var season = episodeItem.Season;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user