mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Merge branch 'master' into sort-nfo-data
This commit is contained in:
commit
a5f3d942f6
@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.2",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
6
.github/workflows/ci-codeql-analysis.yml
vendored
6
.github/workflows/ci-codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/init@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/autobuild@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@dd746615b3b9d728a6a37ca2045b68ca76d4841a # v3.28.8
|
||||
uses: github/codeql-action/analyze@6bb031afdd8eb862ea3fc1848194185e076637e5 # v3.28.11
|
||||
|
8
.github/workflows/ci-compat.yml
vendored
8
.github/workflows/ci-compat.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: abi-head
|
||||
retention-days: 14
|
||||
@ -65,7 +65,7 @@ jobs:
|
||||
dotnet build Jellyfin.Server -o ./out
|
||||
|
||||
- name: Upload Head
|
||||
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: abi-base
|
||||
retention-days: 14
|
||||
@ -85,13 +85,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download abi-head
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: abi-head
|
||||
path: abi-head
|
||||
|
||||
- name: Download abi-base
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: abi-base
|
||||
path: abi-base
|
||||
|
16
.github/workflows/ci-openapi.yml
vendored
16
.github/workflows/ci-openapi.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
- 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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: openapi-head
|
||||
retention-days: 14
|
||||
@ -61,7 +61,7 @@ jobs:
|
||||
- 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@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
|
||||
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1
|
||||
with:
|
||||
name: openapi-base
|
||||
retention-days: 14
|
||||
@ -80,12 +80,12 @@ jobs:
|
||||
- openapi-base
|
||||
steps:
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
- name: Download openapi-base
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: openapi-base
|
||||
path: openapi-base
|
||||
@ -158,7 +158,7 @@ jobs:
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
@ -172,7 +172,7 @@ jobs:
|
||||
strip_components: 1
|
||||
target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||
- name: Move openapi.json (unstable) into place
|
||||
uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
|
||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
@ -220,7 +220,7 @@ jobs:
|
||||
run: |-
|
||||
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
|
||||
- name: Download openapi-head
|
||||
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
|
||||
uses: actions/download-artifact@cc203385981b70ca67e1cc392babf9cc229d5806 # v4.1.9
|
||||
with:
|
||||
name: openapi-head
|
||||
path: openapi-head
|
||||
@ -234,7 +234,7 @@ jobs:
|
||||
strip_components: 1
|
||||
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
|
||||
- name: Move openapi.json (stable) into place
|
||||
uses: appleboy/ssh-action@7eaf76671a0d7eec5d98ee897acda4f968735a17 # v1.2.0
|
||||
uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
|
||||
with:
|
||||
host: "${{ secrets.REPO_HOST }}"
|
||||
username: "${{ secrets.REPO_USER }}"
|
||||
|
2
.github/workflows/ci-tests.yml
vendored
2
.github/workflows/ci-tests.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
||||
--verbosity minimal
|
||||
|
||||
- name: Merge code coverage results
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@c38c522d4b391c1b0da979cbb2e902c0a252a7dc # v5.4.3
|
||||
uses: danielpalme/ReportGenerator-GitHub-Action@f1927db1dbfc029b056583ee488832e939447fe6 # v5.4.4
|
||||
with:
|
||||
reports: "**/coverage.cobertura.xml"
|
||||
targetdir: "merged/"
|
||||
|
88
.github/workflows/commands.yml
vendored
88
.github/workflows/commands.yml
vendored
@ -34,94 +34,6 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
||||
check-backport:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
name: Check Backport
|
||||
if: ${{ ( github.event.issue.pull_request && contains(github.event.comment.body, '@jellyfin-bot check backport') ) || github.event.label.name == 'stable backport' || contains(github.event.pull_request.labels.*.name, 'stable backport' ) }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
reactions: eyes
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Notify as running
|
||||
id: comment_running
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Running backport tests...
|
||||
|
||||
- name: Perform test backport
|
||||
id: run_tests
|
||||
run: |
|
||||
set +o errexit
|
||||
git config --global user.name "Jellyfin Bot"
|
||||
git config --global user.email "team@jellyfin.org"
|
||||
CURRENT_BRANCH="origin/${GITHUB_HEAD_REF}"
|
||||
git checkout master
|
||||
git merge --no-ff ${CURRENT_BRANCH}
|
||||
MERGE_COMMIT_HASH=$( git log -q -1 | head -1 | awk '{ print $2 }' )
|
||||
git fetch --all
|
||||
CURRENT_STABLE=$( git branch -r | grep 'origin/release' | sort -rV | head -1 | awk -F '/' '{ print $NF }' )
|
||||
stable_branch="Current stable release branch: ${CURRENT_STABLE}"
|
||||
echo ${stable_branch}
|
||||
echo ::set-output name=branch::${stable_branch}
|
||||
git checkout -t origin/${CURRENT_STABLE} -b ${CURRENT_STABLE}
|
||||
git cherry-pick -sx -m1 ${MERGE_COMMIT_HASH} &>output.txt
|
||||
retcode=$?
|
||||
cat output.txt | grep -v 'hint:'
|
||||
output="$( grep -v 'hint:' output.txt )"
|
||||
output="${output//'%'/'%25'}"
|
||||
output="${output//$'\n'/'%0A'}"
|
||||
output="${output//$'\r'/'%0D'}"
|
||||
echo ::set-output name=output::$output
|
||||
exit ${retcode}
|
||||
|
||||
- name: Notify with result success
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null && success() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ steps.comment_running.outputs.comment-id }}
|
||||
body: |
|
||||
${{ steps.run_tests.outputs.branch }}
|
||||
Output from `git cherry-pick`:
|
||||
|
||||
---
|
||||
|
||||
${{ steps.run_tests.outputs.output }}
|
||||
reactions: hooray
|
||||
|
||||
- name: Notify with result failure
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null && failure() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ steps.comment_running.outputs.comment-id }}
|
||||
body: |
|
||||
${{ steps.run_tests.outputs.branch }}
|
||||
Output from `git cherry-pick`:
|
||||
|
||||
---
|
||||
|
||||
${{ steps.run_tests.outputs.output }}
|
||||
reactions: confused
|
||||
|
||||
rename:
|
||||
name: Rename
|
||||
if: contains(github.event.comment.body, '@jellyfin-bot rename') && github.event.comment.author_association == 'MEMBER'
|
||||
|
@ -16,38 +16,38 @@
|
||||
<PackageVersion Include="Diacritics" Version="3.3.29" />
|
||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||
<PackageVersion Include="FsCheck.Xunit" Version="3.0.1" />
|
||||
<PackageVersion Include="FsCheck.Xunit" Version="3.1.0" />
|
||||
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.3" />
|
||||
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
|
||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
|
||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||
<PackageVersion Include="libse" Version="4.0.10" />
|
||||
<PackageVersion Include="LrcParser" Version="2024.0728.2" />
|
||||
<PackageVersion Include="LrcParser" Version="2025.228.1" />
|
||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.1" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageVersion Include="MimeTypes" Version="2.5.2" />
|
||||
<PackageVersion Include="Moq" Version="4.18.4" />
|
||||
<PackageVersion Include="NEbml" Version="0.12.0" />
|
||||
@ -75,11 +75,11 @@
|
||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.1" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.1" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.1" />
|
||||
<PackageVersion Include="System.Text.Encoding.CodePages" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
|
||||
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="9.0.2" />
|
||||
<PackageVersion Include="TagLibSharp" Version="2.3.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="6.15.0" />
|
||||
<PackageVersion Include="z440.atl.core" Version="6.19.0" />
|
||||
<PackageVersion Include="TMDbLib" Version="2.2.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||
|
@ -34,76 +34,46 @@ namespace Emby.Server.Implementations.AppBase
|
||||
DataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the program data folder.
|
||||
/// </summary>
|
||||
/// <value>The program data path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string ProgramDataPath { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string WebPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system folder.
|
||||
/// </summary>
|
||||
/// <value>The path to the system folder.</value>
|
||||
/// <inheritdoc/>
|
||||
public string ProgramSystemPath { get; } = AppContext.BaseDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the data directory.
|
||||
/// </summary>
|
||||
/// <value>The data directory.</value>
|
||||
/// <inheritdoc/>
|
||||
public string DataPath { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string VirtualDataPath => "%AppDataPath%";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the image cache path.
|
||||
/// </summary>
|
||||
/// <value>The image cache path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string ImageCachePath => Path.Combine(CachePath, "images");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin directory.
|
||||
/// </summary>
|
||||
/// <value>The plugins path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string PluginsPath => Path.Combine(ProgramDataPath, "plugins");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the plugin configurations directory.
|
||||
/// </summary>
|
||||
/// <value>The plugin configurations path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string PluginConfigurationsPath => Path.Combine(PluginsPath, "configurations");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the log directory.
|
||||
/// </summary>
|
||||
/// <value>The log directory path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string LogDirectoryPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the application configuration root directory.
|
||||
/// </summary>
|
||||
/// <value>The configuration directory path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string ConfigurationDirectoryPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to the system configuration file.
|
||||
/// </summary>
|
||||
/// <value>The system configuration file path.</value>
|
||||
/// <inheritdoc/>
|
||||
public string SystemConfigurationFilePath => Path.Combine(ConfigurationDirectoryPath, "system.xml");
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the folder path to the cache directory.
|
||||
/// </summary>
|
||||
/// <value>The cache directory.</value>
|
||||
/// <inheritdoc/>
|
||||
public string CachePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the folder path to the temp directory within the cache folder.
|
||||
/// </summary>
|
||||
/// <value>The temp directory.</value>
|
||||
/// <inheritdoc/>
|
||||
public string TempDirectory => Path.Join(Path.GetTempPath(), "jellyfin");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string TrickplayPath => Path.Combine(DataPath, "trickplay");
|
||||
}
|
||||
}
|
||||
|
@ -204,7 +204,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
{
|
||||
if (_libraryManager.GetItemById(collectionId) is not BoxSet collection)
|
||||
{
|
||||
throw new ArgumentException("No collection exists with the supplied Id");
|
||||
throw new ArgumentException("No collection exists with the supplied collectionId " + collectionId);
|
||||
}
|
||||
|
||||
List<BaseItem>? itemList = null;
|
||||
@ -218,7 +218,7 @@ namespace Emby.Server.Implementations.Collections
|
||||
|
||||
if (item is null)
|
||||
{
|
||||
throw new ArgumentException("No item exists with the supplied Id");
|
||||
throw new ArgumentException("No item exists with the supplied Id " + id);
|
||||
}
|
||||
|
||||
if (!currentLinkedChildrenIds.Contains(id))
|
||||
|
@ -457,6 +457,7 @@ namespace Emby.Server.Implementations.Library
|
||||
foreach (var child in children)
|
||||
{
|
||||
_itemRepository.DeleteItem(child.Id);
|
||||
_cache.TryRemove(child.Id, out _);
|
||||
}
|
||||
|
||||
_cache.TryRemove(item.Id, out _);
|
||||
@ -1811,11 +1812,11 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <inheritdoc />
|
||||
public void CreateItem(BaseItem item, BaseItem? parent)
|
||||
{
|
||||
CreateOrUpdateItems(new[] { item }, parent, CancellationToken.None);
|
||||
CreateItems(new[] { item }, parent, CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void CreateOrUpdateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
|
||||
public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
|
||||
{
|
||||
_itemRepository.SaveItems(items, cancellationToken);
|
||||
|
||||
@ -2630,15 +2631,6 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
episode.ParentIndexNumber = season.IndexNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
Anime series don't generally have a season in their file name, however,
|
||||
TVDb needs a season to correctly get the metadata.
|
||||
Hence, a null season needs to be filled with something. */
|
||||
// FIXME perhaps this would be better for TVDb parser to ask for season 1 if no season is specified
|
||||
episode.ParentIndexNumber = 1;
|
||||
}
|
||||
|
||||
if (episode.ParentIndexNumber.HasValue)
|
||||
{
|
||||
@ -2972,11 +2964,11 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
if (createEntity)
|
||||
{
|
||||
CreateOrUpdateItems([personEntity], null, CancellationToken.None);
|
||||
CreateItems([personEntity], null, CancellationToken.None);
|
||||
}
|
||||
|
||||
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
|
||||
CreateOrUpdateItems([personEntity], null, CancellationToken.None);
|
||||
CreateItems([personEntity], null, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,14 +43,26 @@ public class SplashscreenPostScanTask : ILibraryPostScanTask
|
||||
/// <inheritdoc />
|
||||
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var posters = GetItemsWithImageType(ImageType.Primary).Select(x => x.GetImages(ImageType.Primary).First().Path).ToList();
|
||||
var backdrops = GetItemsWithImageType(ImageType.Thumb).Select(x => x.GetImages(ImageType.Thumb).First().Path).ToList();
|
||||
var posters = GetItemsWithImageType(ImageType.Primary)
|
||||
.Select(x => x.GetImages(ImageType.Primary).FirstOrDefault()?.Path)
|
||||
.Where(path => !string.IsNullOrEmpty(path))
|
||||
.Select(path => path!)
|
||||
.ToList();
|
||||
var backdrops = GetItemsWithImageType(ImageType.Thumb)
|
||||
.Select(x => x.GetImages(ImageType.Thumb).FirstOrDefault()?.Path)
|
||||
.Where(path => !string.IsNullOrEmpty(path))
|
||||
.Select(path => path!)
|
||||
.ToList();
|
||||
if (backdrops.Count == 0)
|
||||
{
|
||||
// Thumb images fit better because they include the title in the image but are not provided with TMDb.
|
||||
// Using backdrops as a fallback to generate an image at all
|
||||
_logger.LogDebug("No thumb images found. Using backdrops to generate splashscreen");
|
||||
backdrops = GetItemsWithImageType(ImageType.Backdrop).Select(x => x.GetImages(ImageType.Backdrop).First().Path).ToList();
|
||||
backdrops = GetItemsWithImageType(ImageType.Backdrop)
|
||||
.Select(x => x.GetImages(ImageType.Backdrop).FirstOrDefault()?.Path)
|
||||
.Where(path => !string.IsNullOrEmpty(path))
|
||||
.Select(path => path!)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
_imageEncoder.CreateSplashscreen(posters, backdrops);
|
||||
|
@ -134,5 +134,7 @@
|
||||
"TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة",
|
||||
"TaskDownloadMissingLyricsDescription": "كلمات",
|
||||
"TaskExtractMediaSegments": "فحص مقاطع الوسائط",
|
||||
"TaskExtractMediaSegmentsDescription": "وسائط"
|
||||
"TaskExtractMediaSegmentsDescription": "يستخرج مقاطع وسائط من إضافات MediaSegment المُفعّلة.",
|
||||
"TaskMoveTrickplayImages": "تغيير مكان صور المعاينة السريعة",
|
||||
"TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة."
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"Sync": "Сінхранізаваць",
|
||||
"Playlists": "Плэйлісты",
|
||||
"Playlists": "Спісы прайгравання",
|
||||
"Latest": "Апошні",
|
||||
"LabelIpAddressValue": "IP-адрас: {0}",
|
||||
"ItemAddedWithName": "{0} быў дададзены ў бібліятэку",
|
||||
@ -16,7 +16,7 @@
|
||||
"Collections": "Калекцыі",
|
||||
"Default": "Па змаўчанні",
|
||||
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
|
||||
"Folders": "Папкі",
|
||||
"Folders": "Тэчкі",
|
||||
"Favorites": "Абранае",
|
||||
"External": "Знешні",
|
||||
"Genres": "Жанры",
|
||||
|
@ -16,7 +16,7 @@
|
||||
"Folders": "Carpetes",
|
||||
"Genres": "Gèneres",
|
||||
"HeaderAlbumArtists": "Artistes de l'àlbum",
|
||||
"HeaderContinueWatching": "Continuar veient",
|
||||
"HeaderContinueWatching": "Continua veient",
|
||||
"HeaderFavoriteAlbums": "Àlbums preferits",
|
||||
"HeaderFavoriteArtists": "Artistes preferits",
|
||||
"HeaderFavoriteEpisodes": "Episodis preferits",
|
||||
@ -24,13 +24,13 @@
|
||||
"HeaderFavoriteSongs": "Cançons preferides",
|
||||
"HeaderLiveTV": "TV en directe",
|
||||
"HeaderNextUp": "A continuació",
|
||||
"HeaderRecordingGroups": "Grups d'enregistrament",
|
||||
"HeaderRecordingGroups": "Grups Musicals",
|
||||
"HomeVideos": "Vídeos domèstics",
|
||||
"Inherit": "Hereta",
|
||||
"ItemAddedWithName": "{0} ha sigut afegit a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} ha sigut eliminat de la biblioteca",
|
||||
"Inherit": "Heretat",
|
||||
"ItemAddedWithName": "{0} s'ha afegit a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} s'ha eliminat de la biblioteca",
|
||||
"LabelIpAddressValue": "Adreça IP: {0}",
|
||||
"LabelRunningTimeValue": "Temps en funcionament: {0}",
|
||||
"LabelRunningTimeValue": "Temps en marxa: {0}",
|
||||
"Latest": "Darrers",
|
||||
"MessageApplicationUpdated": "El servidor de Jellyfin ha estat actualitzat",
|
||||
"MessageApplicationUpdatedTo": "El servidor de Jellyfin ha estat actualitzat a {0}",
|
||||
@ -44,8 +44,8 @@
|
||||
"NameSeasonNumber": "Temporada {0}",
|
||||
"NameSeasonUnknown": "Temporada desconeguda",
|
||||
"NewVersionIsAvailable": "Una nova versió del servidor de Jellyfin està disponible per a descarregar.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualització de l'aplicació disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualització de l'aplicació instal·lada",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Actualització de l'aplicatiu disponible",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Actualització de l'aplicatiu instal·lada",
|
||||
"NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada",
|
||||
"NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada",
|
||||
"NotificationOptionCameraImageUploaded": "Imatge de càmera pujada",
|
||||
@ -54,8 +54,8 @@
|
||||
"NotificationOptionPluginError": "Un complement ha fallat",
|
||||
"NotificationOptionPluginInstalled": "Complement instal·lat",
|
||||
"NotificationOptionPluginUninstalled": "Complement desinstal·lat",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualització de complement instal·lada",
|
||||
"NotificationOptionServerRestartRequired": "Reinici del servidor requerit",
|
||||
"NotificationOptionPluginUpdateInstalled": "Actualització del complement instal·lada",
|
||||
"NotificationOptionServerRestartRequired": "El servidor s'ha de reiniciar",
|
||||
"NotificationOptionTaskFailed": "Tasca programada fallida",
|
||||
"NotificationOptionUserLockedOut": "Usuari expulsat",
|
||||
"NotificationOptionVideoPlayback": "Reproducció de vídeo iniciada",
|
||||
@ -64,15 +64,15 @@
|
||||
"Playlists": "Llistes de reproducció",
|
||||
"Plugin": "Complement",
|
||||
"PluginInstalledWithName": "{0} ha estat instal·lat",
|
||||
"PluginUninstalledWithName": "{0} ha estat desinstal·lat",
|
||||
"PluginUpdatedWithName": "{0} ha estat actualitzat",
|
||||
"PluginUninstalledWithName": "S'ha instalat {0}",
|
||||
"PluginUpdatedWithName": "S'ha actualitzat {0}",
|
||||
"ProviderValue": "Proveïdor: {0}",
|
||||
"ScheduledTaskFailedWithName": "{0} ha fallat",
|
||||
"ScheduledTaskStartedWithName": "{0} s'ha iniciat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
|
||||
"ScheduledTaskStartedWithName": "S'ha iniciat {0}",
|
||||
"ServerNameNeedsToBeRestarted": "S'ha de reiniciar {0}",
|
||||
"Shows": "Sèries",
|
||||
"Songs": "Cançons",
|
||||
"StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu-ho altre cop aviat.",
|
||||
"StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu de nou en una estona.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Els subtítols per a {1} no s'han pogut baixar de {0}",
|
||||
"Sync": "Sincronitzar",
|
||||
@ -80,41 +80,41 @@
|
||||
"TvShows": "Sèries de TV",
|
||||
"User": "Usuari",
|
||||
"UserCreatedWithName": "S'ha creat l'usuari {0}",
|
||||
"UserDeletedWithName": "L'usuari {0} ha estat eliminat",
|
||||
"UserDeletedWithName": "S'ha eliminat l'usuari {0}",
|
||||
"UserDownloadingItemWithValues": "{0} està descarregant {1}",
|
||||
"UserLockedOutWithName": "L'usuari {0} ha sigut expulsat",
|
||||
"UserLockedOutWithName": "S'ha expulsat a l'usuari {0}",
|
||||
"UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
|
||||
"UserOnlineFromDevice": "{0} està connectat des de {1}",
|
||||
"UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}",
|
||||
"UserPasswordChangedWithName": "S'ha canviat la contrasenya per a l'usuari {0}",
|
||||
"UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per a {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva biblioteca",
|
||||
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1} a {2}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1} a {2}",
|
||||
"ValueHasBeenAddedToLibrary": "S'ha afegit {0} a la teva biblioteca",
|
||||
"ValueSpecialEpisodeName": "Especial - {0}",
|
||||
"VersionNumber": "Versió {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.",
|
||||
"TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin",
|
||||
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'internet.",
|
||||
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals per internet.",
|
||||
"TaskRefreshChannels": "Actualitza els canals",
|
||||
"TaskCleanTranscodeDescription": "Elimina els arxius de transcodificacions que tinguin més d'un dia.",
|
||||
"TaskCleanTranscode": "Neteja les transcodificacions",
|
||||
"TaskUpdatePluginsDescription": "Actualitza els connectors que estan configurats per a actualitzar-se automàticament.",
|
||||
"TaskUpdatePlugins": "Actualitza els connectors",
|
||||
"TaskRefreshPeopleDescription": "Actualitza les metadades dels actors i directors de la teva mediateca.",
|
||||
"TaskUpdatePluginsDescription": "Actualitza els complements que estan configurats per a actualitzar-se automàticament.",
|
||||
"TaskUpdatePlugins": "Actualitza els complements",
|
||||
"TaskRefreshPeopleDescription": "Actualitza les metadades dels actors i directors de la teva biblioteca de mitjans.",
|
||||
"TaskRefreshPeople": "Actualitza les persones",
|
||||
"TaskCleanLogsDescription": "Esborra els logs que tinguin més de {0} dies.",
|
||||
"TaskCleanLogs": "Neteja els registres",
|
||||
"TaskRefreshLibraryDescription": "Escaneja la mediateca buscant fitxers nous i refresca les metadades.",
|
||||
"TaskRefreshLibraryDescription": "Escaneja la biblioteca de mitjans buscant fitxers nous i refresca les metadades.",
|
||||
"TaskRefreshLibrary": "Escaneja la biblioteca de mitjans",
|
||||
"TaskRefreshChapterImagesDescription": "Crea les miniatures dels vídeos que tinguin capítols.",
|
||||
"TaskRefreshChapterImages": "Extreure les imatges dels capítols",
|
||||
"TaskCleanCacheDescription": "Elimina els arxius temporals que ja no són necessaris per al servidor.",
|
||||
"TaskCleanCache": "Elimina arxius temporals",
|
||||
"TasksChannelsCategory": "Canals d'internet",
|
||||
"TasksApplicationCategory": "Aplicació",
|
||||
"TaskCleanCacheDescription": "Elimina la memòria cau no necessària per al servidor.",
|
||||
"TaskCleanCache": "Elimina la memòria cau",
|
||||
"TasksChannelsCategory": "Canals per internet",
|
||||
"TasksApplicationCategory": "Aplicatiu",
|
||||
"TasksLibraryCategory": "Biblioteca",
|
||||
"TasksMaintenanceCategory": "Manteniment",
|
||||
"TaskCleanActivityLogDescription": "Eliminat entrades del registre d'activitats mes antigues que l'antiguitat configurada.",
|
||||
"TaskCleanActivityLogDescription": "Eliminades les entrades del registre d'activitats més antigues que l'antiguitat configurada.",
|
||||
"TaskCleanActivityLog": "Buidar el registre d'activitat",
|
||||
"Undefined": "Indefinit",
|
||||
"Forced": "Forçat",
|
||||
@ -128,11 +128,11 @@
|
||||
"TaskRefreshTrickplayImages": "Generar miniatures de línia de temps",
|
||||
"TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades.",
|
||||
"TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.",
|
||||
"TaskCleanCollectionsAndPlaylists": "Neteja col·leccions i llistes de reproducció",
|
||||
"TaskAudioNormalization": "Normalització d'Àudio",
|
||||
"TaskAudioNormalizationDescription": "Escaneja arxius per dades de normalització d'àudio.",
|
||||
"TaskDownloadMissingLyricsDescription": "Baixar lletres de les cançons",
|
||||
"TaskDownloadMissingLyrics": "Baixar lletres que falten",
|
||||
"TaskCleanCollectionsAndPlaylists": "Neteja les col·leccions i llistes de reproducció",
|
||||
"TaskAudioNormalization": "Estabilització d'Àudio",
|
||||
"TaskAudioNormalizationDescription": "Escaneja arxius per dades d'estabilització d'àudio.",
|
||||
"TaskDownloadMissingLyricsDescription": "Baixar les lletres de les cançons",
|
||||
"TaskDownloadMissingLyrics": "Baixar les lletres que falten",
|
||||
"TaskExtractMediaSegments": "Escaneig de segments multimèdia",
|
||||
"TaskExtractMediaSegmentsDescription": "Extreu o obté segments multimèdia usant els connectors MediaSegment activats.",
|
||||
"TaskMoveTrickplayImages": "Migra la ubicació de la imatge de Trickplay",
|
||||
|
@ -11,7 +11,7 @@
|
||||
"Collections": "Συλλογές",
|
||||
"DeviceOfflineWithName": "Ο/Η {0} αποσυνδέθηκε",
|
||||
"DeviceOnlineWithName": "Ο/Η {0} συνδέθηκε",
|
||||
"FailedLoginAttemptWithUserName": "Αποτυχημένη προσπάθεια σύνδεσης από {0}",
|
||||
"FailedLoginAttemptWithUserName": "Αποτυχία προσπάθειας σύνδεσης από {0}",
|
||||
"Favorites": "Αγαπημένα",
|
||||
"Folders": "Φάκελοι",
|
||||
"Genres": "Είδη",
|
||||
@ -28,7 +28,7 @@
|
||||
"HomeVideos": "Προσωπικά Βίντεο",
|
||||
"Inherit": "Κληρονόμηση",
|
||||
"ItemAddedWithName": "{0} προστέθηκε στη βιβλιοθήκη",
|
||||
"ItemRemovedWithName": "{0} διαγράφηκε από τη βιβλιοθήκη",
|
||||
"ItemRemovedWithName": "Το {0} διαγράφτηκε από τη βιβλιοθήκη",
|
||||
"LabelIpAddressValue": "Διεύθυνση IP: {0}",
|
||||
"LabelRunningTimeValue": "Διάρκεια: {0}",
|
||||
"Latest": "Πρόσφατα",
|
||||
@ -96,7 +96,7 @@
|
||||
"TaskCleanLogsDescription": "Διαγράφει αρχεία καταγραφής που είναι πάνω από {0} ημέρες.",
|
||||
"TaskCleanLogs": "Εκκαθάριση Καταλόγου Καταγραφής",
|
||||
"TaskRefreshLibraryDescription": "Σαρώνει την βιβλιοθήκη πολυμέσων σας για νέα αρχεία και ανανεώνει τα μεταδεδομένα.",
|
||||
"TaskRefreshLibrary": "Βιβλιοθήκη Σάρωσης Πολυμέσων",
|
||||
"TaskRefreshLibrary": "Σάρωση Βιβλιοθήκης Πολυμέσων",
|
||||
"TaskRefreshChapterImagesDescription": "Δημιουργεί μικρογραφίες για βίντεο που έχουν κεφάλαια.",
|
||||
"TaskRefreshChapterImages": "Εξαγωγή Εικόνων Κεφαλαίου",
|
||||
"TaskCleanCacheDescription": "Διαγράφει αρχεία προσωρινής μνήμης που δεν χρειάζονται πλέον το σύστημα.",
|
||||
|
@ -19,25 +19,25 @@
|
||||
"Artists": "Artistak",
|
||||
"Albums": "Albumak",
|
||||
"TaskOptimizeDatabase": "Datu basea optimizatu",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Metadataren konfigurazioan oinarrituta falta diren azpitituluak bilatzen ditu interneten.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Falta diren azpitituluak bilatzen ditu interneten metadatuen konfigurazioaren arabera.",
|
||||
"TaskDownloadMissingSubtitles": "Falta diren azpitituluak deskargatu",
|
||||
"TaskRefreshChannelsDescription": "Internet kanalen informazioa eguneratu.",
|
||||
"TaskRefreshChannels": "Kanalak eguneratu",
|
||||
"TaskCleanTranscodeDescription": "Egun bat baino zaharragoak diren transcode fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanTranscode": "Transcode direktorioa garbitu",
|
||||
"TaskUpdatePluginsDescription": "Automatikoki eguneratzeko konfiguratutako pluginen eguneraketak deskargatu eta instalatzen ditu.",
|
||||
"TaskCleanTranscodeDescription": "Egun bat baino zaharragoak diren transkodifikazio fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanTranscode": "Transkodifikazio direktorioa garbitu",
|
||||
"TaskUpdatePluginsDescription": "Automatikoki deskargatu eta instalatu eguneraketak konfiguratutako pluginetarako.",
|
||||
"TaskUpdatePlugins": "Pluginak eguneratu",
|
||||
"TaskRefreshPeopleDescription": "Zure liburutegiko aktore eta zuzendarien metadata eguneratzen du.",
|
||||
"TaskRefreshPeopleDescription": "Zure liburutegiko aktore eta zuzendarien metadatuak eguneratzen ditu.",
|
||||
"TaskRefreshPeople": "Jendea eguneratu",
|
||||
"TaskCleanLogsDescription": "{0} egun baino zaharragoak diren log fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanLogs": "Log direktorioa garbitu",
|
||||
"TaskRefreshLibraryDescription": "Zure multimedia liburutegia eskaneatzen du fitxategi berriak eta metadatak eguneratzeko.",
|
||||
"TaskRefreshLibrary": "Multimedia Liburutegia eskaneatu",
|
||||
"TaskRefreshLibraryDescription": "Zure multimedia liburutegia eskaneatzen du fitxategi berriak eta metadatuak eguneratzeko.",
|
||||
"TaskRefreshLibrary": "Multimedia liburutegia eskaneatu",
|
||||
"TaskRefreshChapterImagesDescription": "Kapituluak dituzten bideoen miniaturak sortzen ditu.",
|
||||
"TaskRefreshChapterImages": "Kapituluen irudiak erauzi",
|
||||
"TaskCleanCacheDescription": "Sistemak behar ez dituen cache fitxategiak ezabatzen ditu.",
|
||||
"TaskCleanCache": "Cache Directorioa garbitu",
|
||||
"TaskCleanActivityLogDescription": "Konfiguratuta data baino zaharragoak diren log-ak ezabatu.",
|
||||
"TaskCleanCache": "Cache direktorioa garbitu",
|
||||
"TaskCleanActivityLogDescription": "Konfiguratutako baino zaharragoak diren jarduera-log sarrerak ezabatzen ditu.",
|
||||
"TaskCleanActivityLog": "Erabilera Log-a garbitu",
|
||||
"TasksChannelsCategory": "Internet Kanalak",
|
||||
"TasksApplicationCategory": "Aplikazioa",
|
||||
@ -45,22 +45,22 @@
|
||||
"TasksMaintenanceCategory": "Mantenua",
|
||||
"VersionNumber": "Bertsioa {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} zure multimedia liburutegian gehitu da",
|
||||
"UserStoppedPlayingItemWithValues": "{0}-ek {1} ikusteaz bukatu du {2}-(a)n",
|
||||
"UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(a)n",
|
||||
"UserPolicyUpdatedWithName": "{0} Erabiltzailearen politikak aldatu dira",
|
||||
"UserPasswordChangedWithName": "{0} Erabiltzailearen pasahitza aldatu da",
|
||||
"UserOnlineFromDevice": "{0} online dago {1}-tik",
|
||||
"UserOfflineFromDevice": "{0} {1}-tik deskonektatu da",
|
||||
"UserLockedOutWithName": "{0} Erabiltzailea blokeatu da",
|
||||
"UserDownloadingItemWithValues": "{1} {0}-tik deskargatzen",
|
||||
"UserStoppedPlayingItemWithValues": "{0} {1} ikusten bukatu du {2}-(e)n",
|
||||
"UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(e)n",
|
||||
"UserPolicyUpdatedWithName": "{0} erabiltzailearen politikak aldatu dira",
|
||||
"UserPasswordChangedWithName": "{0} erabiltzailearen pasahitza aldatu da",
|
||||
"UserOnlineFromDevice": "{0} online dago {1}-(e)tik",
|
||||
"UserOfflineFromDevice": "{0} {1}-(e)tik deskonektatu da",
|
||||
"UserLockedOutWithName": "{0} erabiltzailea blokeatu da",
|
||||
"UserDownloadingItemWithValues": "{0} {1} deskargatzen ari da",
|
||||
"UserDeletedWithName": "{0} Erabiltzailea ezabatu da",
|
||||
"UserCreatedWithName": "{0} Erabiltzailea sortu da",
|
||||
"User": "Erabiltzailea",
|
||||
"Undefined": "Ezezaguna",
|
||||
"TvShows": "TB showak",
|
||||
"TvShows": "TB serieak",
|
||||
"System": "Sistema",
|
||||
"SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0} deskargatzean huts egin du",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduxeago.",
|
||||
"SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0}-tik deskargatzeak huts egin du",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduago.",
|
||||
"ServerNameNeedsToBeRestarted": "{0} berrabiarazi behar da",
|
||||
"ScheduledTaskStartedWithName": "{0} hasi da",
|
||||
"ScheduledTaskFailedWithName": "{0} huts egin du",
|
||||
@ -89,26 +89,26 @@
|
||||
"NameSeasonNumber": "{0} Denboraldia",
|
||||
"NameInstallFailed": "{0} instalazioak huts egin du",
|
||||
"Music": "Musika",
|
||||
"MixedContent": "Denetariko edukia",
|
||||
"MixedContent": "Eduki mistoa",
|
||||
"MessageServerConfigurationUpdated": "Zerbitzariaren konfigurazioa eguneratu da",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Zerbitzariaren konfigurazio {0} atala eguneratu da",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Zerbitzariaren {0} konfigurazio atala eguneratu da",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin zerbitzaria {0}-ra eguneratu da",
|
||||
"MessageApplicationUpdated": "Jellyfin zerbitzaria eguneratu da",
|
||||
"Latest": "Azkena",
|
||||
"LabelRunningTimeValue": "Denbora martxan: {0}",
|
||||
"LabelRunningTimeValue": "Iraupena: {0}",
|
||||
"LabelIpAddressValue": "IP helbidea: {0}",
|
||||
"ItemRemovedWithName": "{0} liburutegitik ezabatu da",
|
||||
"ItemRemovedWithName": "{0} liburutegitik kendu da",
|
||||
"ItemAddedWithName": "{0} liburutegira gehitu da",
|
||||
"HomeVideos": "Etxeko bideoak",
|
||||
"HeaderNextUp": "Nobedadeak",
|
||||
"HeaderNextUp": "Hurrengoa",
|
||||
"HeaderLiveTV": "Zuzeneko TB",
|
||||
"HeaderFavoriteSongs": "Gogoko abestiak",
|
||||
"HeaderFavoriteShows": "Gogoko showak",
|
||||
"HeaderFavoriteShows": "Gogoko serieak",
|
||||
"HeaderFavoriteEpisodes": "Gogoko atalak",
|
||||
"HeaderFavoriteArtists": "Gogoko artistak",
|
||||
"HeaderFavoriteAlbums": "Gogoko albumak",
|
||||
"Forced": "Behartuta",
|
||||
"FailedLoginAttemptWithUserName": "Login egiten akatsa, saiatu hemen {0}",
|
||||
"FailedLoginAttemptWithUserName": "{0}-tik saioa hasteak huts egin du",
|
||||
"External": "Kanpokoa",
|
||||
"DeviceOnlineWithName": "{0} konektatu da",
|
||||
"DeviceOfflineWithName": "{0} deskonektatu da",
|
||||
@ -117,13 +117,23 @@
|
||||
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
|
||||
"Application": "Aplikazioa",
|
||||
"AppDeviceValues": "App: {0}, Gailua: {1}",
|
||||
"HearingImpaired": "Entzunaldia aldatua",
|
||||
"HearingImpaired": "Entzumen urritasuna",
|
||||
"ProviderValue": "Hornitzailea: {0}",
|
||||
"TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.",
|
||||
"HeaderRecordingGroups": "Grabaketa taldeak",
|
||||
"Inherit": "Oinordetu",
|
||||
"TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.",
|
||||
"TaskKeyframeExtractor": "Fotograma gakoen erauzgailua",
|
||||
"TaskRefreshTrickplayImages": "\"Trickplay Irudiak Sortu",
|
||||
"TaskRefreshTrickplayImagesDescription": "Bideoentzako trickplay aurrebistak sortzen ditu gaitutako liburutegietan."
|
||||
"TaskRefreshTrickplayImages": "Trickplay irudiak sortu",
|
||||
"TaskRefreshTrickplayImagesDescription": "Bideoentzako trickplay aurrebistak sortzen ditu gaitutako liburutegietan.",
|
||||
"TaskAudioNormalization": "Audio normalizazioa",
|
||||
"TaskDownloadMissingLyrics": "Deskargatu falta diren letrak",
|
||||
"TaskDownloadMissingLyricsDescription": "Deskargatu abestientzako letrak",
|
||||
"TaskExtractMediaSegments": "Multimedia segmentuen eskaneoa",
|
||||
"TaskCleanCollectionsAndPlaylistsDescription": "Jada existitzen ez diren bildumak eta erreprodukzio-zerrendak kentzen ditu.",
|
||||
"TaskCleanCollectionsAndPlaylists": "Garbitu bildumak eta erreprodukzio-zerrendak",
|
||||
"TaskExtractMediaSegmentsDescription": "Media segmentuak atera edo lortzen ditu MediaSegment gaituta duten pluginetik.",
|
||||
"TaskMoveTrickplayImages": "Aldatu Trickplay irudien kokalekua",
|
||||
"TaskMoveTrickplayImagesDescription": "Lehendik dauden trickplay fitxategiak liburutegiaren ezarpenen arabera mugitzen dira.",
|
||||
"TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu."
|
||||
}
|
||||
|
3
Emby.Server.Implementations/Localization/Core/ht.json
Normal file
3
Emby.Server.Implementations/Localization/Core/ht.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Books": "liv"
|
||||
}
|
@ -13,7 +13,7 @@
|
||||
"DeviceOnlineWithName": "{0} belépett",
|
||||
"FailedLoginAttemptWithUserName": "Sikertelen bejelentkezési kísérlet innen: {0}",
|
||||
"Favorites": "Kedvencek",
|
||||
"Folders": "Könyvtárak",
|
||||
"Folders": "Mappák",
|
||||
"Genres": "Műfajok",
|
||||
"HeaderAlbumArtists": "Albumelőadók",
|
||||
"HeaderContinueWatching": "Megtekintés folytatása",
|
||||
|
@ -58,8 +58,8 @@
|
||||
"NotificationOptionServerRestartRequired": "Riavvio del server necessario",
|
||||
"NotificationOptionTaskFailed": "Operazione pianificata fallita",
|
||||
"NotificationOptionUserLockedOut": "Utente bloccato",
|
||||
"NotificationOptionVideoPlayback": "La riproduzione video è iniziata",
|
||||
"NotificationOptionVideoPlaybackStopped": "La riproduzione video è stata interrotta",
|
||||
"NotificationOptionVideoPlayback": "Riproduzione video iniziata",
|
||||
"NotificationOptionVideoPlaybackStopped": "Riproduzione video interrotta",
|
||||
"Photos": "Foto",
|
||||
"Playlists": "Playlist",
|
||||
"Plugin": "Plugin",
|
||||
|
139
Emby.Server.Implementations/Localization/Core/lb.json
Normal file
139
Emby.Server.Implementations/Localization/Core/lb.json
Normal file
@ -0,0 +1,139 @@
|
||||
{
|
||||
"Albums": "Alben",
|
||||
"Application": "Applikatioun",
|
||||
"Artists": "Kënschtler",
|
||||
"Books": "Bicher",
|
||||
"Channels": "Kanäl",
|
||||
"Collections": "Kollektiounen",
|
||||
"Default": "Standard",
|
||||
"ChapterNameValue": "Kapitel {0}",
|
||||
"DeviceOnlineWithName": "{0} ass Online",
|
||||
"DeviceOfflineWithName": "{0} ass Offline",
|
||||
"External": "Extern",
|
||||
"Favorites": "Favoritten",
|
||||
"Folders": "Dossieren",
|
||||
"Forced": "Forcéiert",
|
||||
"HeaderAlbumArtists": "Album Kënschtler",
|
||||
"HeaderFavoriteAlbums": "Léifsten Alben",
|
||||
"HeaderFavoriteArtists": "Léifsten Kënschtler",
|
||||
"HeaderFavoriteEpisodes": "Léifsten Episoden",
|
||||
"HeaderFavoriteShows": "Léifsten Shows",
|
||||
"HeaderFavoriteSongs": "Léifsten Lidder",
|
||||
"Genres": "Generen",
|
||||
"HeaderContinueWatching": "Weider kucken",
|
||||
"Inherit": "Iwwerhuelen",
|
||||
"HeaderNextUp": "Als Nächst",
|
||||
"HeaderRecordingGroups": "Opname Gruppen",
|
||||
"HearingImpaired": "Daaf",
|
||||
"HomeVideos": "Amateur Videoen",
|
||||
"ItemRemovedWithName": "Element ewech geholl: {0}",
|
||||
"LabelIpAddressValue": "IP Adress: {0}",
|
||||
"LabelRunningTimeValue": "Lafzäit: {0}",
|
||||
"Latest": "Dat Aktuellst",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin Server aktualiséiert op {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server Konfiguratiounssektioun {0} aktualiséiert",
|
||||
"MessageServerConfigurationUpdated": "Server Konfiguratioun aktualiséiert",
|
||||
"Movies": "Filmer",
|
||||
"Music": "Musek",
|
||||
"NameInstallFailed": "{0} Installatioun net gelongen",
|
||||
"NameSeasonNumber": "Staffel {0}",
|
||||
"NameSeasonUnknown": "Staffel Onbekannt",
|
||||
"MusicVideos": "Museksvideoen",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Applikatiouns Update verfügbar",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Applikatiouns Update nët Installéiert",
|
||||
"NotificationOptionAudioPlayback": "Audio ofspillen gestart",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audio ofspillen gestoppt",
|
||||
"NotificationOptionCameraImageUploaded": "Kamera Bild eropgelueden",
|
||||
"NotificationOptionInstallationFailed": "Installatioun net gelongen",
|
||||
"NotificationOptionNewLibraryContent": "Neien Bibliothéik Inhalt",
|
||||
"NotificationOptionPluginError": "Plugin Feeler",
|
||||
"NotificationOptionPluginInstalled": "Plugin installéiert",
|
||||
"NotificationOptionPluginUninstalled": "Plugin desinstalléiert",
|
||||
"NotificationOptionPluginUpdateInstalled": "Plugin Update installéiert",
|
||||
"Photos": "Fotoen",
|
||||
"NotificationOptionTaskFailed": "Aufgab net gelongen",
|
||||
"NotificationOptionUserLockedOut": "Benotzer Gesperrt",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video ofspillen gestoppt",
|
||||
"NotificationOptionVideoPlayback": "Video ofspillen gestartet",
|
||||
"Plugin": "Plugin",
|
||||
"PluginUninstalledWithName": "{0} desinstalléiert",
|
||||
"PluginUpdatedWithName": "{0} aktualiséiert",
|
||||
"ProviderValue": "Provider: {0}",
|
||||
"ScheduledTaskFailedWithName": "Aufgab: {0} net gelongen",
|
||||
"Playlists": "Playlëschten",
|
||||
"Shows": "Shows",
|
||||
"Songs": "Lidder",
|
||||
"ServerNameNeedsToBeRestarted": "{0} muss nei gestart ginn",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server luedt. Probéier méi spéit nach eng Kéier.",
|
||||
"Sync": "Synchroniséieren",
|
||||
"System": "System",
|
||||
"User": "Benotzer",
|
||||
"TvShows": "TV Shows",
|
||||
"Undefined": "Net definéiert",
|
||||
"UserCreatedWithName": "Benotzer {0} erstellt",
|
||||
"UserDownloadingItemWithValues": "{0} luet {1} erof",
|
||||
"UserOfflineFromDevice": "{0} Benotzer Offline um Gerät {1}",
|
||||
"UserLockedOutWithName": "Benotzer {0} gesperrt",
|
||||
"UserOnlineFromDevice": "{0} Benotzer Online um Gerät {1}",
|
||||
"UserPasswordChangedWithName": "Benotzer Passwuert geännert fir {0}",
|
||||
"UserPolicyUpdatedWithName": "Benotzer Politik aktualiséiert fir: {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} spillt {1} op {2} oof",
|
||||
"ValueHasBeenAddedToLibrary": "{0} der Bibliothéik bäigefüügt",
|
||||
"VersionNumber": "Versioun {0}",
|
||||
"TasksMaintenanceCategory": "Ënnerhalt",
|
||||
"TasksLibraryCategory": "Bibliothéik",
|
||||
"ValueSpecialEpisodeName": "Spezial-Episodenumm",
|
||||
"TasksChannelsCategory": "Internet Kanäl",
|
||||
"TaskCleanActivityLog": "Aktivitéits Log botzen",
|
||||
"TaskCleanActivityLogDescription": "Läscht Aktivitéitslogs méi al wéi konfiguréiert.",
|
||||
"TaskCleanCache": "Aufgab Cache Botzen",
|
||||
"TaskRefreshChapterImages": "Kapitel Biller erstellen",
|
||||
"TaskRefreshChapterImagesDescription": "Erstellt Miniaturbiller fir Videoen, déi Kapitelen hunn.",
|
||||
"TaskAudioNormalization": "Audio Normaliséierung",
|
||||
"TaskRefreshLibrary": "Bibliothéik aktualiséieren",
|
||||
"TaskRefreshLibraryDescription": "Scannt deng Mediebibliothéik no neien Dateien a frëscht d’Metadata op.",
|
||||
"TaskCleanLogs": "Log Dateien botzen",
|
||||
"TaskRefreshPeople": "Persounen aktualiséieren",
|
||||
"TaskRefreshPeopleDescription": "Aktualiséiert Metadata fir Schauspiller a Regisseuren an denger Mediebibliothéik.",
|
||||
"TaskRefreshTrickplayImagesDescription": "Erstellt Trickplay-Viraussiichten fir Videoen an aktivéierte Bibliothéiken.",
|
||||
"TaskCleanTranscode": "Transkodéieren botzen",
|
||||
"TaskCleanTranscodeDescription": "Läscht Transkodéierungsdateien, déi méi al wéi een Dag sinn.",
|
||||
"TaskRefreshChannels": "Kanäl aktualiséieren",
|
||||
"TaskDownloadMissingLyrics": "Fehlend Liddertexter eroflueden",
|
||||
"TaskDownloadMissingLyricsDescription": "Lued Liddertexter fir Lidder erof",
|
||||
"TaskDownloadMissingSubtitles": "Fehlend Ënnertitelen eroflueden",
|
||||
"TaskOptimizeDatabase": "Datebank optiméieren",
|
||||
"TaskKeyframeExtractor": "Schlësselbild Extrakter",
|
||||
"TaskCleanCollectionsAndPlaylists": "Sammlungen a Playlisten botzen",
|
||||
"TaskCleanCollectionsAndPlaylistsDescription": "Ewechhuele vun Elementer aus Sammlungen a Playlisten, déi net méi existéieren.",
|
||||
"TaskExtractMediaSegments": "Mediesegment-Scan",
|
||||
"NewVersionIsAvailable": "Nei Versioun fir Jellyfin Server ass verfügbar.",
|
||||
"CameraImageUploadedFrom": "En neit Kamera Bild gouf vu {0} eropgelueden",
|
||||
"PluginInstalledWithName": "{0} installéiert",
|
||||
"TaskMoveTrickplayImagesDescription": "Verschëfft existent Trickplay-Dateien no de Bibliothéik-Astellungen.",
|
||||
"AppDeviceValues": "App: {0}, Geräter: {1}",
|
||||
"FailedLoginAttemptWithUserName": "Net Gelongen Umeldung {0}",
|
||||
"HeaderLiveTV": "LiveTV",
|
||||
"ItemAddedWithName": "Element derbäi gesat: {0}",
|
||||
"NotificationOptionServerRestartRequired": "Server Restart Erfuerderlech",
|
||||
"ScheduledTaskStartedWithName": "Aufgab: {0} gestart",
|
||||
"AuthenticationSucceededWithUserName": "{0} Authentifikatioun gelongen",
|
||||
"MixedContent": "Gemëschten Inhalt",
|
||||
"MessageApplicationUpdated": "Jellyfin Server Aktualiséiert",
|
||||
"SubtitleDownloadFailureFromForItem": "Ënnertitel Download Feeler vun {0} fir {1}",
|
||||
"TaskCleanLogsDescription": "Läscht Log-Dateien, déi méi al wéi {0} Deeg sinn.",
|
||||
"TaskUpdatePlugins": "Plugins aktualiséieren",
|
||||
"UserDeletedWithName": "Benotzer {0} geläscht",
|
||||
"TasksApplicationCategory": "Applikatioun",
|
||||
"TaskCleanCacheDescription": "Läscht Cache-Dateien, déi net méi vum System gebraucht ginn.",
|
||||
"UserStoppedPlayingItemWithValues": "{0} ass mat {1} op {2} fäerdeg",
|
||||
"TaskAudioNormalizationDescription": "Scannt Dateien no Donnéeën fir d’Audio-Normaliséierung.",
|
||||
"TaskRefreshTrickplayImages": "Trickplay-Biller generéieren",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Sicht am Internet no fehlenden Ënnertitelen op Basis vun der Metadata-Konfiguratioun.",
|
||||
"TaskMoveTrickplayImages": "Trickplay-Biller-Plaz migréieren",
|
||||
"TaskUpdatePluginsDescription": "Lued Aktualiséierungen erof a installéiert se fir Plugins, déi fir automatesch Updates konfiguréiert sinn.",
|
||||
"TaskKeyframeExtractorDescription": "Extrahéiert Schlësselbiller aus Videodateien, fir méi präzis HLS-Playlisten ze erstellen. Dës Aufgab kann eng längere Zäit daueren.",
|
||||
"TaskRefreshChannelsDescription": "Aktualiséiert Informatiounen iwwer Internetkanäl.",
|
||||
"TaskExtractMediaSegmentsDescription": "Extrahéiert oder kritt Mediesegmenter aus Plugins, déi MediaSegment ënnerstëtzen.",
|
||||
"TaskOptimizeDatabaseDescription": "Kompriméiert d’Datebank a schneit de fräie Speicherplatz zou. Dës Aufgab no engem Bibliothéik-Scan oder anere Ännerungen, déi Datebankmodifikatioune mat sech bréngen, auszeféieren, kann d’Performance verbesseren."
|
||||
}
|
@ -286,8 +286,10 @@ namespace Emby.Server.Implementations.Localization
|
||||
}
|
||||
|
||||
// Fairly common for some users to have "Rated R" in their rating field
|
||||
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Rated:", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Trim();
|
||||
|
||||
// Use rating system matching the language
|
||||
if (!string.IsNullOrEmpty(countryCode))
|
||||
|
@ -310,7 +310,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
var item = playlist.LinkedChildren.FirstOrDefault(i => string.Equals(entryId, i.ItemId?.ToString("N", CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase));
|
||||
if (item is null)
|
||||
{
|
||||
_logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", item.ItemId, playlistId);
|
||||
_logger.LogWarning("Modified item not found in playlist. ItemId: {ItemId}, PlaylistId: {PlaylistId}", entryId, playlistId);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -116,6 +116,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
{
|
||||
a.LUFS = await CalculateLUFSAsync(
|
||||
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
|
||||
OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
@ -142,7 +143,10 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
continue;
|
||||
}
|
||||
|
||||
t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken).ConfigureAwait(false);
|
||||
t.LUFS = await CalculateLUFSAsync(
|
||||
string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)),
|
||||
false,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_itemRepository.SaveItems(tracks, cancellationToken);
|
||||
@ -162,7 +166,7 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
];
|
||||
}
|
||||
|
||||
private async Task<float?> CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
|
||||
private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
|
||||
{
|
||||
var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
|
||||
|
||||
@ -189,18 +193,28 @@ public partial class AudioNormalizationTask : IScheduledTask
|
||||
}
|
||||
|
||||
using var reader = process.StandardError;
|
||||
float? lufs = null;
|
||||
await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
|
||||
{
|
||||
Match match = LUFSRegex().Match(line);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||
lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogError("Failed to find LUFS value in output");
|
||||
return null;
|
||||
if (lufs is null)
|
||||
{
|
||||
_logger.LogError("Failed to find LUFS value in output");
|
||||
}
|
||||
|
||||
if (waitForExit)
|
||||
{
|
||||
await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return lufs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace Emby.Server.Implementations.Session
|
||||
private readonly SessionInfo _session;
|
||||
|
||||
private readonly List<IWebSocketConnection> _sockets;
|
||||
private readonly ReaderWriterLockSlim _socketsLock;
|
||||
private bool _disposed = false;
|
||||
|
||||
public WebSocketController(
|
||||
@ -31,10 +32,26 @@ namespace Emby.Server.Implementations.Session
|
||||
_logger = logger;
|
||||
_session = session;
|
||||
_sessionManager = sessionManager;
|
||||
_sockets = new List<IWebSocketConnection>();
|
||||
_sockets = new();
|
||||
_socketsLock = new();
|
||||
}
|
||||
|
||||
private bool HasOpenSockets => GetActiveSockets().Any();
|
||||
private bool HasOpenSockets
|
||||
{
|
||||
get
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
try
|
||||
{
|
||||
_socketsLock.EnterReadLock();
|
||||
return _sockets.Any(i => i.State == WebSocketState.Open);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool SupportsMediaControl => HasOpenSockets;
|
||||
@ -42,23 +59,38 @@ namespace Emby.Server.Implementations.Session
|
||||
/// <inheritdoc />
|
||||
public bool IsSessionActive => HasOpenSockets;
|
||||
|
||||
private IEnumerable<IWebSocketConnection> GetActiveSockets()
|
||||
=> _sockets.Where(i => i.State == WebSocketState.Open);
|
||||
|
||||
public void AddWebSocket(IWebSocketConnection connection)
|
||||
{
|
||||
_logger.LogDebug("Adding websocket to session {Session}", _session.Id);
|
||||
_sockets.Add(connection);
|
||||
|
||||
connection.Closed += OnConnectionClosed;
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
try
|
||||
{
|
||||
_socketsLock.EnterWriteLock();
|
||||
_sockets.Add(connection);
|
||||
connection.Closed += OnConnectionClosed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnConnectionClosed(object? sender, EventArgs e)
|
||||
{
|
||||
var connection = sender as IWebSocketConnection ?? throw new ArgumentException($"{nameof(sender)} is not of type {nameof(IWebSocketConnection)}", nameof(sender));
|
||||
_logger.LogDebug("Removing websocket from session {Session}", _session.Id);
|
||||
_sockets.Remove(connection);
|
||||
connection.Closed -= OnConnectionClosed;
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
try
|
||||
{
|
||||
_socketsLock.EnterWriteLock();
|
||||
_sockets.Remove(connection);
|
||||
connection.Closed -= OnConnectionClosed;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
await _sessionManager.CloseIfNeededAsync(_session).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -69,7 +101,17 @@ namespace Emby.Server.Implementations.Session
|
||||
T data,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var socket = GetActiveSockets().MaxBy(i => i.LastActivityDate);
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
IWebSocketConnection? socket;
|
||||
try
|
||||
{
|
||||
_socketsLock.EnterReadLock();
|
||||
socket = _sockets.Where(i => i.State == WebSocketState.Open).MaxBy(i => i.LastActivityDate);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitReadLock();
|
||||
}
|
||||
|
||||
if (socket is null)
|
||||
{
|
||||
@ -94,12 +136,23 @@ namespace Emby.Server.Implementations.Session
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var socket in _sockets)
|
||||
try
|
||||
{
|
||||
socket.Closed -= OnConnectionClosed;
|
||||
socket.Dispose();
|
||||
_socketsLock.EnterWriteLock();
|
||||
foreach (var socket in _sockets)
|
||||
{
|
||||
socket.Closed -= OnConnectionClosed;
|
||||
socket.Dispose();
|
||||
}
|
||||
|
||||
_sockets.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_socketsLock.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
@ -110,12 +163,23 @@ namespace Emby.Server.Implementations.Session
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var socket in _sockets)
|
||||
try
|
||||
{
|
||||
socket.Closed -= OnConnectionClosed;
|
||||
await socket.DisposeAsync().ConfigureAwait(false);
|
||||
_socketsLock.EnterWriteLock();
|
||||
foreach (var socket in _sockets)
|
||||
{
|
||||
socket.Closed -= OnConnectionClosed;
|
||||
await socket.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_sockets.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_socketsLock.ExitWriteLock();
|
||||
}
|
||||
|
||||
_socketsLock.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
@ -50,20 +50,21 @@ namespace Jellyfin.Api.Auth
|
||||
}
|
||||
|
||||
var role = UserRoles.User;
|
||||
if (authorizationInfo.IsApiKey || authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||
if (authorizationInfo.IsApiKey
|
||||
|| (authorizationInfo.User?.HasPermission(PermissionKind.IsAdministrator) ?? false))
|
||||
{
|
||||
role = UserRoles.Administrator;
|
||||
}
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.Name, authorizationInfo.User.Username),
|
||||
new Claim(ClaimTypes.Name, authorizationInfo.User?.Username ?? string.Empty),
|
||||
new Claim(ClaimTypes.Role, role),
|
||||
new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)),
|
||||
new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId),
|
||||
new Claim(InternalClaimTypes.Device, authorizationInfo.Device),
|
||||
new Claim(InternalClaimTypes.Client, authorizationInfo.Client),
|
||||
new Claim(InternalClaimTypes.Version, authorizationInfo.Version),
|
||||
new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId ?? string.Empty),
|
||||
new Claim(InternalClaimTypes.Device, authorizationInfo.Device ?? string.Empty),
|
||||
new Claim(InternalClaimTypes.Client, authorizationInfo.Client ?? string.Empty),
|
||||
new Claim(InternalClaimTypes.Version, authorizationInfo.Version ?? string.Empty),
|
||||
new Claim(InternalClaimTypes.Token, authorizationInfo.Token),
|
||||
new Claim(InternalClaimTypes.IsApiKey, authorizationInfo.IsApiKey.ToString(CultureInfo.InvariantCulture))
|
||||
};
|
||||
|
@ -91,31 +91,31 @@ public class ArtistsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
@ -295,31 +295,31 @@ public class ArtistsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
|
@ -121,10 +121,10 @@ public class ChannelsController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -197,9 +197,9 @@ public class ChannelsController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
|
@ -50,7 +50,7 @@ public class CollectionController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<CollectionCreationResult>> CreateCollection(
|
||||
[FromQuery] string? name,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] ids,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery] bool isLocked = false)
|
||||
{
|
||||
@ -86,7 +86,7 @@ public class CollectionController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> AddToCollection(
|
||||
[FromRoute, Required] Guid collectionId,
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
|
||||
{
|
||||
await _collectionManager.AddToCollectionAsync(collectionId, ids).ConfigureAwait(true);
|
||||
return NoContent();
|
||||
@ -103,7 +103,7 @@ public class CollectionController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> RemoveFromCollection(
|
||||
[FromRoute, Required] Guid collectionId,
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
|
||||
{
|
||||
await _collectionManager.RemoveFromCollectionAsync(collectionId, ids).ConfigureAwait(false);
|
||||
return NoContent();
|
||||
|
@ -50,8 +50,8 @@ public class FilterController : BaseJellyfinApiController
|
||||
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -137,7 +137,7 @@ public class FilterController : BaseJellyfinApiController
|
||||
public ActionResult<QueryFilters> GetQueryFilters(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isAiring,
|
||||
[FromQuery] bool? isMovie,
|
||||
[FromQuery] bool? isSports,
|
||||
|
@ -76,18 +76,18 @@ public class GenresController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
|
@ -73,11 +73,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -117,11 +117,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -161,11 +161,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -203,11 +203,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] string name,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -241,11 +241,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -285,11 +285,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -330,11 +330,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromQuery, Required] Guid id,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
return GetInstantMixFromArtists(
|
||||
id,
|
||||
@ -368,11 +368,11 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromQuery, Required] Guid id,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
|
@ -171,8 +171,8 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? hasParentalRating,
|
||||
[FromQuery] bool? isHd,
|
||||
[FromQuery] bool? is4K,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] bool? isUnaired,
|
||||
[FromQuery] double? minCommunityRating,
|
||||
@ -190,42 +190,42 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isNews,
|
||||
[FromQuery] bool? isKids,
|
||||
[FromQuery] bool? isSports,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? recursive,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery] string? minOfficialRating,
|
||||
[FromQuery] bool? isLocked,
|
||||
[FromQuery] bool? isPlaceHolder,
|
||||
@ -236,12 +236,12 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] int? maxWidth,
|
||||
[FromQuery] int? maxHeight,
|
||||
[FromQuery] bool? is3D,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
{
|
||||
@ -638,8 +638,8 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? hasParentalRating,
|
||||
[FromQuery] bool? isHd,
|
||||
[FromQuery] bool? is4K,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] bool? isUnaired,
|
||||
[FromQuery] double? minCommunityRating,
|
||||
@ -657,42 +657,42 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isNews,
|
||||
[FromQuery] bool? isKids,
|
||||
[FromQuery] bool? isSports,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? recursive,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery] string? minOfficialRating,
|
||||
[FromQuery] bool? isLocked,
|
||||
[FromQuery] bool? isPlaceHolder,
|
||||
@ -703,12 +703,12 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] int? maxWidth,
|
||||
[FromQuery] int? maxHeight,
|
||||
[FromQuery] bool? is3D,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
=> GetItems(
|
||||
@ -827,13 +827,13 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool excludeActiveSessions = false)
|
||||
@ -929,13 +929,13 @@ public class ItemsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool excludeActiveSessions = false)
|
||||
|
@ -144,8 +144,8 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool inheritFromParent = false,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -218,8 +218,8 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool inheritFromParent = false,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
@ -290,8 +290,8 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool inheritFromParent = false,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[]? sortOrder = null)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[]? sortBy = null,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[]? sortOrder = null)
|
||||
{
|
||||
var themeSongs = GetThemeSongs(
|
||||
itemId,
|
||||
@ -400,7 +400,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
|
||||
{
|
||||
var isApiKey = User.GetIsApiKey();
|
||||
var userId = User.GetUserId();
|
||||
@ -722,10 +722,10 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.IsNullOrEmpty()
|
||||
|
@ -77,7 +77,7 @@ public class LibraryStructureController : BaseJellyfinApiController
|
||||
public async Task<ActionResult> AddVirtualFolder(
|
||||
[FromQuery] string name,
|
||||
[FromQuery] CollectionTypeOptions? collectionType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] paths,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] paths,
|
||||
[FromBody] AddVirtualFolderDto? libraryOptionsDto,
|
||||
[FromQuery] bool refreshLibrary = false)
|
||||
{
|
||||
|
@ -159,10 +159,10 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isDisliked,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery] SortOrder? sortOrder,
|
||||
[FromQuery] bool enableFavoriteSorting = false,
|
||||
[FromQuery] bool addCurrentProgram = true)
|
||||
@ -283,8 +283,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool? isMovie,
|
||||
[FromQuery] bool? isSeries,
|
||||
@ -371,8 +371,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
@ -566,7 +566,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] channelIds,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] DateTime? minStartDate,
|
||||
[FromQuery] bool? hasAired,
|
||||
@ -581,17 +581,17 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isSports,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] string? seriesTimerId,
|
||||
[FromQuery] Guid? librarySeriesId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
@ -730,9 +730,9 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isSports,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
|
@ -65,7 +65,7 @@ public class MoviesController : BaseJellyfinApiController
|
||||
public ActionResult<IEnumerable<RecommendationDto>> GetMovieRecommendations(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] int categoryLimit = 5,
|
||||
[FromQuery] int itemLimit = 8)
|
||||
{
|
||||
|
@ -76,18 +76,18 @@ public class MusicGenresController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] bool? enableImages = true,
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
|
@ -67,14 +67,14 @@ public class PersonsController : BaseJellyfinApiController
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetPersons(
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludePersonTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] excludePersonTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery] Guid? appearsInItemId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
|
@ -76,7 +76,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
|
||||
[FromQuery, ParameterObsolete] string? name,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
|
||||
[FromQuery, ParameterObsolete] Guid? userId,
|
||||
[FromQuery, ParameterObsolete] MediaType? mediaType,
|
||||
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest)
|
||||
@ -370,7 +370,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> AddItemToPlaylist(
|
||||
[FromRoute, Required] Guid playlistId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
||||
[FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
@ -446,7 +446,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult> RemoveItemFromPlaylist(
|
||||
[FromRoute, Required] string playlistId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] entryIds)
|
||||
{
|
||||
var callingUserId = User.GetUserId();
|
||||
|
||||
@ -493,11 +493,11 @@ public class PlaylistsController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
var callingUserId = userId ?? User.GetUserId();
|
||||
var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
|
||||
|
@ -84,9 +84,9 @@ public class SearchController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery, Required] string searchTerm,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery] bool? isMovie,
|
||||
[FromQuery] bool? isSeries,
|
||||
|
@ -122,7 +122,7 @@ public class SessionController : BaseJellyfinApiController
|
||||
public async Task<ActionResult> Play(
|
||||
[FromRoute, Required] string sessionId,
|
||||
[FromQuery, Required] PlayCommand playCommand,
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] itemIds,
|
||||
[FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] itemIds,
|
||||
[FromQuery] long? startPositionTicks,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] int? audioStreamIndex,
|
||||
@ -347,8 +347,8 @@ public class SessionController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<ActionResult> PostCapabilities(
|
||||
[FromQuery] string? id,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] playableMediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] GeneralCommandType[] supportedCommands,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] playableMediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] GeneralCommandType[] supportedCommands,
|
||||
[FromQuery] bool supportsMediaControl = false,
|
||||
[FromQuery] bool supportsPersistentIdentifier = true)
|
||||
{
|
||||
|
@ -73,13 +73,13 @@ public class StudiosController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
|
@ -59,8 +59,8 @@ public class SuggestionsController : BaseJellyfinApiController
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool enableTotalRecordCount = false)
|
||||
@ -115,8 +115,8 @@ public class SuggestionsController : BaseJellyfinApiController
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetSuggestionsLegacy(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] type,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaType,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] type,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool enableTotalRecordCount = false)
|
||||
|
@ -212,20 +212,4 @@ public class SystemController : BaseJellyfinApiController
|
||||
FileStream stream = new FileStream(file.FullName, FileMode.Open, FileAccess.Read, fileShare, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||
return File(stream, "text/plain; charset=utf-8");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets wake on lan information.
|
||||
/// </summary>
|
||||
/// <response code="200">Information retrieved.</response>
|
||||
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
|
||||
[HttpGet("WakeOnLanInfo")]
|
||||
[Authorize]
|
||||
[Obsolete("This endpoint is obsolete.")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
|
||||
{
|
||||
var result = _networkManager.GetMacAddresses()
|
||||
.Select(i => new WakeOnLanInfo(i));
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
|
@ -130,8 +130,8 @@ public class TrailersController : BaseJellyfinApiController
|
||||
[FromQuery] bool? hasParentalRating,
|
||||
[FromQuery] bool? isHd,
|
||||
[FromQuery] bool? is4K,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] bool? isUnaired,
|
||||
[FromQuery] double? minCommunityRating,
|
||||
@ -149,41 +149,41 @@ public class TrailersController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isNews,
|
||||
[FromQuery] bool? isKids,
|
||||
[FromQuery] bool? isSports,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? recursive,
|
||||
[FromQuery] string? searchTerm,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
|
||||
[FromQuery] bool? isFavorite,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] int[] years,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
|
||||
[FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] string? person,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] studios,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] artists,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] albums,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
|
||||
[FromQuery] string? minOfficialRating,
|
||||
[FromQuery] bool? isLocked,
|
||||
[FromQuery] bool? isPlaceHolder,
|
||||
@ -194,12 +194,12 @@ public class TrailersController : BaseJellyfinApiController
|
||||
[FromQuery] int? maxWidth,
|
||||
[FromQuery] int? maxHeight,
|
||||
[FromQuery] bool? is3D,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
|
||||
[FromQuery] string? nameStartsWithOrGreater,
|
||||
[FromQuery] string? nameStartsWith,
|
||||
[FromQuery] string? nameLessThan,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
{
|
||||
|
@ -77,12 +77,12 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] Guid? seriesId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] DateTime? nextUpDateCutoff,
|
||||
[FromQuery] bool enableTotalRecordCount = true,
|
||||
@ -143,11 +143,11 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
@ -208,7 +208,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetEpisodes(
|
||||
[FromRoute, Required] Guid seriesId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] int? season,
|
||||
[FromQuery] Guid? seasonId,
|
||||
[FromQuery] bool? isMissing,
|
||||
@ -218,7 +218,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] ItemSortBy? sortBy)
|
||||
{
|
||||
@ -332,13 +332,13 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetSeasons(
|
||||
[FromRoute, Required] Guid seriesId,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery] bool? isSpecialSeason,
|
||||
[FromQuery] bool? isMissing,
|
||||
[FromQuery] Guid? adjacentTo,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
|
@ -98,7 +98,7 @@ public class UniversalAudioController : BaseJellyfinApiController
|
||||
[ProducesAudioFile]
|
||||
public async Task<ActionResult> GetUniversalAudioStream(
|
||||
[FromRoute, Required] Guid itemId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] container,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] container,
|
||||
[FromQuery] string? mediaSourceId,
|
||||
[FromQuery] string? deviceId,
|
||||
[FromQuery] Guid? userId,
|
||||
|
@ -523,12 +523,12 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int limit = 20,
|
||||
[FromQuery] bool groupItems = true)
|
||||
@ -608,12 +608,12 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMediaLegacy(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery] bool? isPlayed,
|
||||
[FromQuery] bool? enableImages,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int limit = 20,
|
||||
[FromQuery] bool groupItems = true)
|
||||
|
@ -66,7 +66,7 @@ public class UserViewsController : BaseJellyfinApiController
|
||||
public QueryResult<BaseItemDto> GetUserViews(
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool? includeExternalContent,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews,
|
||||
[FromQuery] bool includeHidden = false)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
@ -110,7 +110,7 @@ public class UserViewsController : BaseJellyfinApiController
|
||||
public QueryResult<BaseItemDto> GetUserViewsLegacy(
|
||||
[FromRoute, Required] Guid userId,
|
||||
[FromQuery] bool? includeExternalContent,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] CollectionType?[] presetViews,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] CollectionType?[] presetViews,
|
||||
[FromQuery] bool includeHidden = false)
|
||||
=> GetUserViews(userId, includeExternalContent, presetViews, includeHidden);
|
||||
|
||||
|
@ -184,7 +184,7 @@ public class VideosController : BaseJellyfinApiController
|
||||
[Authorize(Policy = Policies.RequiresElevation)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
|
||||
public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids)
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
var items = ids
|
||||
|
@ -72,16 +72,16 @@ public class YearsController : BaseJellyfinApiController
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetYears(
|
||||
[FromQuery] int? startIndex,
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
|
||||
[FromQuery] Guid? parentId,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
|
||||
[FromQuery] bool? enableUserData,
|
||||
[FromQuery] int? imageTypeLimit,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
|
||||
[FromQuery] Guid? userId,
|
||||
[FromQuery] bool recursive = true,
|
||||
[FromQuery] bool? enableImages = true)
|
||||
|
@ -8,18 +8,18 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Api.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited array model binder.
|
||||
/// Comma delimited collection model binder.
|
||||
/// Returns an empty array of specified type if there is no query parameter.
|
||||
/// </summary>
|
||||
public class CommaDelimitedArrayModelBinder : IModelBinder
|
||||
public class CommaDelimitedCollectionModelBinder : IModelBinder
|
||||
{
|
||||
private readonly ILogger<CommaDelimitedArrayModelBinder> _logger;
|
||||
private readonly ILogger<CommaDelimitedCollectionModelBinder> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommaDelimitedArrayModelBinder"/> class.
|
||||
/// Initializes a new instance of the <see cref="CommaDelimitedCollectionModelBinder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedArrayModelBinder}"/> interface.</param>
|
||||
public CommaDelimitedArrayModelBinder(ILogger<CommaDelimitedArrayModelBinder> logger)
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{CommaDelimitedCollectionModelBinder}"/> interface.</param>
|
||||
public CommaDelimitedCollectionModelBinder(ILogger<CommaDelimitedCollectionModelBinder> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
@ -8,18 +8,18 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Jellyfin.Api.ModelBinders;
|
||||
|
||||
/// <summary>
|
||||
/// Comma delimited array model binder.
|
||||
/// Returns an empty array of specified type if there is no query parameter.
|
||||
/// Comma delimited collection model binder.
|
||||
/// Returns an empty collection of specified type if there is no query parameter.
|
||||
/// </summary>
|
||||
public class PipeDelimitedArrayModelBinder : IModelBinder
|
||||
public class PipeDelimitedCollectionModelBinder : IModelBinder
|
||||
{
|
||||
private readonly ILogger<PipeDelimitedArrayModelBinder> _logger;
|
||||
private readonly ILogger<PipeDelimitedCollectionModelBinder> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PipeDelimitedArrayModelBinder"/> class.
|
||||
/// Initializes a new instance of the <see cref="PipeDelimitedCollectionModelBinder"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedArrayModelBinder}"/> interface.</param>
|
||||
public PipeDelimitedArrayModelBinder(ILogger<PipeDelimitedArrayModelBinder> logger)
|
||||
/// <param name="logger">Instance of the <see cref="ILogger{PipeDelimitedCollectionModelBinder}"/> interface.</param>
|
||||
public PipeDelimitedCollectionModelBinder(ILogger<PipeDelimitedCollectionModelBinder> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
@ -17,7 +17,7 @@ public class GetProgramsDto
|
||||
/// <summary>
|
||||
/// Gets or sets the channels to return guide information for.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<Guid>? ChannelIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -93,25 +93,25 @@ public class GetProgramsDto
|
||||
/// <summary>
|
||||
/// Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<ItemSortBy>? SortBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets sort order.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<SortOrder>? SortOrder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the genres to return guide information for.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonPipeDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonPipeDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<string>? Genres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the genre ids to return guide information for.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<Guid>? GenreIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -133,7 +133,7 @@ public class GetProgramsDto
|
||||
/// <summary>
|
||||
/// Gets or sets the image types to include in the output.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<ImageType>? EnableImageTypes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@ -154,6 +154,6 @@ public class GetProgramsDto
|
||||
/// <summary>
|
||||
/// Gets or sets specify additional fields of information to return in the output.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<ItemFields>? Fields { get; set; }
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ public class CreatePlaylistDto
|
||||
/// <summary>
|
||||
/// Gets or sets item ids to add to the playlist.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<Guid> Ids { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
|
@ -19,7 +19,7 @@ public class UpdatePlaylistDto
|
||||
/// <summary>
|
||||
/// Gets or sets item ids of the playlist.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<Guid>? Ids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -70,7 +70,9 @@ public class ActivityLogWebSocketListener : BasePeriodicWebSocketListener<Activi
|
||||
/// <param name="message">The message.</param>
|
||||
protected override void Start(WebSocketMessageInfo message)
|
||||
{
|
||||
if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||
if (!message.Connection.AuthorizationInfo.IsApiKey
|
||||
&& (message.Connection.AuthorizationInfo.User is null
|
||||
|| !message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)))
|
||||
{
|
||||
throw new AuthenticationException("Only admin users can retrieve the activity log.");
|
||||
}
|
||||
|
@ -79,7 +79,9 @@ public class SessionInfoWebSocketListener : BasePeriodicWebSocketListener<IEnume
|
||||
/// <param name="message">The message.</param>
|
||||
protected override void Start(WebSocketMessageInfo message)
|
||||
{
|
||||
if (!message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator))
|
||||
if (!message.Connection.AuthorizationInfo.IsApiKey
|
||||
&& (message.Connection.AuthorizationInfo.User is null
|
||||
|| !message.Connection.AuthorizationInfo.User.HasPermission(PermissionKind.IsAdministrator)))
|
||||
{
|
||||
throw new AuthenticationException("Only admin users can subscribe to session information.");
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ public class BaseItemEntity
|
||||
|
||||
public string? Path { get; set; }
|
||||
|
||||
public DateTime StartDate { get; set; }
|
||||
public DateTime? StartDate { get; set; }
|
||||
|
||||
public DateTime EndDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
|
||||
public string? ChannelId { get; set; }
|
||||
public Guid? ChannelId { get; set; }
|
||||
|
||||
public bool IsMovie { get; set; }
|
||||
|
||||
|
@ -553,7 +553,7 @@ public sealed class BaseItemRepository
|
||||
dto.Genres = entity.Genres?.Split('|') ?? [];
|
||||
dto.DateCreated = entity.DateCreated.GetValueOrDefault();
|
||||
dto.DateModified = entity.DateModified.GetValueOrDefault();
|
||||
dto.ChannelId = string.IsNullOrWhiteSpace(entity.ChannelId) ? Guid.Empty : (Guid.TryParse(entity.ChannelId, out var channelId) ? channelId : Guid.Empty);
|
||||
dto.ChannelId = entity.ChannelId ?? Guid.Empty;
|
||||
dto.DateLastRefreshed = entity.DateLastRefreshed.GetValueOrDefault();
|
||||
dto.DateLastSaved = entity.DateLastSaved.GetValueOrDefault();
|
||||
dto.OwnerId = string.IsNullOrWhiteSpace(entity.OwnerId) ? Guid.Empty : (Guid.TryParse(entity.OwnerId, out var ownerId) ? ownerId : Guid.Empty);
|
||||
@ -645,7 +645,7 @@ public sealed class BaseItemRepository
|
||||
// dto.MediaType = Enum.TryParse<MediaType>(entity.MediaType);
|
||||
if (dto is IHasStartDate hasStartDate)
|
||||
{
|
||||
hasStartDate.StartDate = entity.StartDate;
|
||||
hasStartDate.StartDate = entity.StartDate.GetValueOrDefault();
|
||||
}
|
||||
|
||||
// Fields that are present in the DB but are never actually used
|
||||
@ -683,12 +683,13 @@ public sealed class BaseItemRepository
|
||||
|
||||
entity.ParentId = !dto.ParentId.IsEmpty() ? dto.ParentId : null;
|
||||
entity.Path = GetPathToSave(dto.Path);
|
||||
entity.EndDate = dto.EndDate.GetValueOrDefault();
|
||||
entity.EndDate = dto.EndDate;
|
||||
entity.CommunityRating = dto.CommunityRating;
|
||||
entity.CustomRating = dto.CustomRating;
|
||||
entity.IndexNumber = dto.IndexNumber;
|
||||
entity.IsLocked = dto.IsLocked;
|
||||
entity.Name = dto.Name;
|
||||
entity.CleanName = GetCleanValue(dto.Name);
|
||||
entity.OfficialRating = dto.OfficialRating;
|
||||
entity.Overview = dto.Overview;
|
||||
entity.ParentIndexNumber = dto.ParentIndexNumber;
|
||||
@ -716,7 +717,7 @@ public sealed class BaseItemRepository
|
||||
entity.Genres = string.Join('|', dto.Genres);
|
||||
entity.DateCreated = dto.DateCreated;
|
||||
entity.DateModified = dto.DateModified;
|
||||
entity.ChannelId = dto.ChannelId.ToString();
|
||||
entity.ChannelId = dto.ChannelId;
|
||||
entity.DateLastRefreshed = dto.DateLastRefreshed;
|
||||
entity.DateLastSaved = dto.DateLastSaved;
|
||||
entity.OwnerId = dto.OwnerId.ToString();
|
||||
@ -821,10 +822,9 @@ public sealed class BaseItemRepository
|
||||
entity.StartDate = hasStartDate.StartDate;
|
||||
}
|
||||
|
||||
entity.UnratedType = dto.GetBlockUnratedType().ToString();
|
||||
|
||||
// Fields that are present in the DB but are never actually used
|
||||
// dto.UnratedType = entity.UnratedType;
|
||||
// dto.TopParentId = entity.TopParentId;
|
||||
// dto.CleanName = entity.CleanName;
|
||||
// dto.UserDataKey = entity.UserDataKey;
|
||||
|
||||
if (dto is Folder folder)
|
||||
@ -854,7 +854,10 @@ public sealed class BaseItemRepository
|
||||
}
|
||||
|
||||
// query = query.DistinctBy(e => e.CleanValue);
|
||||
return query.Select(e => e.ItemValue.CleanValue).ToArray();
|
||||
return query.Select(e => e.ItemValue)
|
||||
.GroupBy(e => e.CleanValue)
|
||||
.Select(e => e.First().Value)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static bool TypeRequiresDeserialization(Type type)
|
||||
@ -1448,8 +1451,7 @@ public sealed class BaseItemRepository
|
||||
|
||||
if (filter.ChannelIds.Count > 0)
|
||||
{
|
||||
var channelIds = filter.ChannelIds.Select(e => e.ToString("N", CultureInfo.InvariantCulture)).ToArray();
|
||||
baseQuery = baseQuery.Where(e => channelIds.Contains(e.ChannelId));
|
||||
baseQuery = baseQuery.Where(e => e.ChannelId != null && filter.ChannelIds.Contains(e.ChannelId.Value));
|
||||
}
|
||||
|
||||
if (!filter.ParentId.IsEmpty())
|
||||
|
@ -88,7 +88,7 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||
query = query.Where(e => e.StreamType == typeValue);
|
||||
}
|
||||
|
||||
return query;
|
||||
return query.OrderBy(e => e.StreamIndex);
|
||||
}
|
||||
|
||||
private MediaStream Map(MediaStreamInfo entity)
|
||||
@ -137,7 +137,7 @@ public class MediaStreamRepository : IMediaStreamRepository
|
||||
dto.ElPresentFlag = entity.ElPresentFlag;
|
||||
dto.BlPresentFlag = entity.BlPresentFlag;
|
||||
dto.DvBlSignalCompatibilityId = entity.DvBlSignalCompatibilityId;
|
||||
dto.IsHearingImpaired = entity.IsHearingImpaired;
|
||||
dto.IsHearingImpaired = entity.IsHearingImpaired.GetValueOrDefault();
|
||||
dto.Rotation = entity.Rotation;
|
||||
|
||||
if (dto.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
|
||||
|
@ -11,6 +11,9 @@ using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Item;
|
||||
#pragma warning disable RS0030 // Do not use banned APIs
|
||||
#pragma warning disable CA1304 // Specify CultureInfo
|
||||
#pragma warning disable CA1311 // Specify a culture or use an invariant version
|
||||
#pragma warning disable CA1862 // Use the 'StringComparison' method overloads to perform case-insensitive string comparisons
|
||||
|
||||
/// <summary>
|
||||
/// Manager for handling people.
|
||||
@ -155,7 +158,8 @@ public class PeopleRepository(IDbContextFactory<JellyfinDbContext> dbProvider, I
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(filter.NameContains))
|
||||
{
|
||||
query = query.Where(e => e.Name.Contains(filter.NameContains));
|
||||
var nameContainsUpper = filter.NameContains.ToUpper();
|
||||
query = query.Where(e => e.Name.ToUpper().Contains(nameContainsUpper));
|
||||
}
|
||||
|
||||
return query;
|
||||
|
@ -4,6 +4,8 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Entities.Security;
|
||||
using Jellyfin.Data.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Implementations;
|
||||
@ -271,4 +273,23 @@ public class JellyfinDbContext(DbContextOptions<JellyfinDbContext> options, ILog
|
||||
// Configuration for each entity is in its own class inside 'ModelConfiguration'.
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(JellyfinDbContext).Assembly);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Conventions.Add(_ => new DoNotUseReturningClauseConvention());
|
||||
}
|
||||
|
||||
private class DoNotUseReturningClauseConvention : IModelFinalizingConvention
|
||||
{
|
||||
public void ProcessModelFinalizing(
|
||||
IConventionModelBuilder modelBuilder,
|
||||
IConventionContext<IConventionModelBuilder> context)
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
|
||||
{
|
||||
entityType.UseSqlReturningClause(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1
Jellyfin.Server.Implementations/Migrations/.gitattributes
vendored
Normal file
1
Jellyfin.Server.Implementations/Migrations/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
JellyfinDbModelSnapshot.cs binary
|
1595
Jellyfin.Server.Implementations/Migrations/20250204092455_MakeStartEndDateNullable.Designer.cs
generated
Normal file
1595
Jellyfin.Server.Implementations/Migrations/20250204092455_MakeStartEndDateNullable.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MakeStartEndDateNullable : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "StartDate",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT");
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "EndDate",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "StartDate",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<DateTime>(
|
||||
name: "EndDate",
|
||||
table: "BaseItems",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified),
|
||||
oldClrType: typeof(DateTime),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
}
|
||||
}
|
||||
}
|
1595
Jellyfin.Server.Implementations/Migrations/20250214031148_ChannelIdGuid.Designer.cs
generated
Normal file
1595
Jellyfin.Server.Implementations/Migrations/20250214031148_ChannelIdGuid.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Jellyfin.Server.Implementations.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class ChannelIdGuid : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// NOOP, Guids and strings are stored the same in SQLite.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// NOOP, Guids and strings are stored the same in SQLite.
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "8.0.10");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.2");
|
||||
|
||||
modelBuilder.Entity("Jellyfin.Data.Entities.AccessSchedule", b =>
|
||||
{
|
||||
@ -152,7 +152,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<int?>("Audio")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ChannelId")
|
||||
b.Property<Guid?>("ChannelId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CleanName")
|
||||
@ -185,7 +185,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<DateTime?>("DateModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("EndDate")
|
||||
b.Property<DateTime?>("EndDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EpisodeTitle")
|
||||
@ -323,7 +323,7 @@ namespace Jellyfin.Server.Implementations.Migrations
|
||||
b.Property<string>("SortName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("StartDate")
|
||||
b.Property<DateTime?>("StartDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Studios")
|
||||
|
@ -116,17 +116,15 @@ namespace Jellyfin.Server.Implementations.Security
|
||||
DeviceId = deviceId,
|
||||
Version = version,
|
||||
Token = token,
|
||||
IsAuthenticated = false,
|
||||
HasToken = false
|
||||
IsAuthenticated = false
|
||||
};
|
||||
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
if (!authInfo.HasToken)
|
||||
{
|
||||
// Request doesn't contain a token.
|
||||
return authInfo;
|
||||
}
|
||||
|
||||
authInfo.HasToken = true;
|
||||
var dbContext = await _jellyfinDbProvider.CreateDbContextAsync().ConfigureAwait(false);
|
||||
await using (dbContext.ConfigureAwait(false))
|
||||
{
|
||||
|
@ -610,9 +610,11 @@ public class TrickplayManager : ITrickplayManager
|
||||
/// <inheritdoc />
|
||||
public string GetTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false)
|
||||
{
|
||||
var basePath = _config.ApplicationPaths.TrickplayPath;
|
||||
var idString = item.Id.ToString("N", CultureInfo.InvariantCulture);
|
||||
var path = saveWithMedia
|
||||
? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay"))
|
||||
: Path.Combine(item.GetInternalMetadataPath(), "trickplay");
|
||||
: Path.Combine(basePath, idString);
|
||||
|
||||
var subdirectory = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
|
@ -113,7 +113,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @
|
||||
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
|
||||
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
|
||||
[GeneratedRegex(@"^[\w\ \-'._@+]+$")]
|
||||
[GeneratedRegex(@"^(?!\s)[\w\ \-'._@+]+(?<!\s)$")]
|
||||
private static partial Regex ValidUsernameRegex();
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -89,7 +89,7 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
Audio, ExternalServiceId, IsInMixedFolder, DateLastSaved, LockedFields, Studios, Tags, TrailerTypes, OriginalTitle, PrimaryVersionId,
|
||||
DateLastMediaAdded, Album, LUFS, NormalizationGain, CriticRating, IsVirtualItem, SeriesName, UserDataKey, SeasonName, SeasonId, SeriesId,
|
||||
PresentationUniqueKey, InheritedParentalRatingValue, ExternalSeriesId, Tagline, ProviderIds, Images, ProductionLocations, ExtraIds, TotalBitrate,
|
||||
ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType FROM TypedBaseItems
|
||||
ExtraType, Artists, AlbumArtists, ExternalId, SeriesPresentationUniqueKey, ShowId, OwnerId, MediaType, SortName, CleanName FROM TypedBaseItems
|
||||
""";
|
||||
dbContext.BaseItems.ExecuteDelete();
|
||||
|
||||
@ -678,7 +678,7 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
entity.EndDate = endDate;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var guid))
|
||||
if (reader.TryGetGuid(index++, out var guid))
|
||||
{
|
||||
entity.ChannelId = guid;
|
||||
}
|
||||
@ -1034,6 +1034,16 @@ public class MigrateLibraryDb : IMigrationRoutine
|
||||
entity.MediaType = mediaType;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var sortName))
|
||||
{
|
||||
entity.SortName = sortName;
|
||||
}
|
||||
|
||||
if (reader.TryGetString(index++, out var cleanName))
|
||||
{
|
||||
entity.CleanName = cleanName;
|
||||
}
|
||||
|
||||
var baseItem = BaseItemRepository.DeserialiseBaseItem(entity, _logger, null, false);
|
||||
var dataKeys = baseItem.GetUserDataKeys();
|
||||
userDataKeys.AddRange(dataKeys);
|
||||
|
@ -39,7 +39,7 @@ public class MoveTrickplayFiles : IMigrationRoutine
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Guid Id => new("4EF123D5-8EFF-4B0B-869D-3AED07A60E1B");
|
||||
public Guid Id => new("9540D44A-D8DC-11EF-9CBB-B77274F77C52");
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "MoveTrickplayFiles";
|
||||
@ -89,6 +89,12 @@ public class MoveTrickplayFiles : IMigrationRoutine
|
||||
{
|
||||
_fileSystem.MoveDirectory(oldPath, newPath);
|
||||
}
|
||||
|
||||
oldPath = GetNewOldTrickplayDirectory(item, trickplayInfo.TileWidth, trickplayInfo.TileHeight, trickplayInfo.Width, false);
|
||||
if (_fileSystem.DirectoryExists(oldPath))
|
||||
{
|
||||
_fileSystem.MoveDirectory(oldPath, newPath);
|
||||
}
|
||||
}
|
||||
} while (previousCount == Limit);
|
||||
|
||||
@ -101,4 +107,20 @@ public class MoveTrickplayFiles : IMigrationRoutine
|
||||
|
||||
return width.HasValue ? Path.Combine(path, width.Value.ToString(CultureInfo.InvariantCulture)) : path;
|
||||
}
|
||||
|
||||
private string GetNewOldTrickplayDirectory(BaseItem item, int tileWidth, int tileHeight, int width, bool saveWithMedia = false)
|
||||
{
|
||||
var path = saveWithMedia
|
||||
? Path.Combine(item.ContainingFolderPath, Path.ChangeExtension(item.Path, ".trickplay"))
|
||||
: Path.Combine(item.GetInternalMetadataPath(), "trickplay");
|
||||
|
||||
var subdirectory = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0} - {1}x{2}",
|
||||
width.ToString(CultureInfo.InvariantCulture),
|
||||
tileWidth.ToString(CultureInfo.InvariantCulture),
|
||||
tileHeight.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return Path.Combine(path, subdirectory);
|
||||
}
|
||||
}
|
||||
|
@ -84,5 +84,11 @@ namespace MediaBrowser.Common.Configuration
|
||||
/// </summary>
|
||||
/// <value>The magic string used for virtual path manipulation.</value>
|
||||
string VirtualDataPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path used for storing trickplay files.
|
||||
/// </summary>
|
||||
/// <value>The trickplay path.</value>
|
||||
string TrickplayPath { get; }
|
||||
}
|
||||
}
|
||||
|
@ -94,12 +94,6 @@ namespace MediaBrowser.Common.Net
|
||||
/// <returns>IP address to use, or loopback address if all else fails.</returns>
|
||||
string GetBindAddress(string source, out int? port);
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all the MAC addresses associated with active interfaces.
|
||||
/// </summary>
|
||||
/// <returns>List of MAC addresses.</returns>
|
||||
IReadOnlyList<PhysicalAddress> GetMacAddresses();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the address is part of the user defined LAN.
|
||||
/// </summary>
|
||||
|
@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
[JsonIgnore]
|
||||
public override SourceType SourceType => SourceType.Channel;
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
var blockedChannelsPreference = user.GetPreferenceValues<Guid>(PreferenceKind.BlockedChannels);
|
||||
if (blockedChannelsPreference.Length != 0)
|
||||
@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Channels
|
||||
}
|
||||
}
|
||||
|
||||
return base.IsVisible(user);
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
|
||||
|
@ -1303,7 +1303,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (GetParents().Any(i => !i.IsVisible(user)))
|
||||
if (GetParents().Any(i => !i.IsVisible(user, true)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1525,13 +1525,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Determines if a given user has access to this item.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param>
|
||||
/// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException">If user is null.</exception>
|
||||
public bool IsParentalAllowed(User user)
|
||||
public bool IsParentalAllowed(User user, bool skipAllowedTagsCheck)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
if (!IsVisibleViaTags(user))
|
||||
if (!IsVisibleViaTags(user, skipAllowedTagsCheck))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1603,7 +1604,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return list.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
}
|
||||
|
||||
private bool IsVisibleViaTags(User user)
|
||||
private bool IsVisibleViaTags(User user, bool skipAllowedTagsCheck)
|
||||
{
|
||||
var allTags = GetInheritedTags();
|
||||
if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
|
||||
@ -1618,7 +1619,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags);
|
||||
if (allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
|
||||
if (!skipAllowedTagsCheck && allowedTagsPreference.Length != 0 && !allowedTagsPreference.Any(i => allTags.Contains(i, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -1658,13 +1659,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Default is just parental allowed. Can be overridden for more functionality.
|
||||
/// </summary>
|
||||
/// <param name="user">The user.</param>
|
||||
/// <param name="skipAllowedTagsCheck">Don't check for allowed tags.</param>
|
||||
/// <returns><c>true</c> if the specified user is visible; otherwise, <c>false</c>.</returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="user" /> is <c>null</c>.</exception>
|
||||
public virtual bool IsVisible(User user)
|
||||
public virtual bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(user);
|
||||
|
||||
return IsParentalAllowed(user);
|
||||
return IsParentalAllowed(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
public virtual bool IsVisibleStandalone(User user)
|
||||
|
@ -96,11 +96,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
return GetLibraryOptions(Path);
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
if (GetLibraryOptions().Enabled)
|
||||
{
|
||||
return base.IsVisible(user);
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -219,7 +219,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
LibraryManager.CreateItem(item, this);
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
if (this is ICollectionFolder && this is not BasePluginFolder)
|
||||
{
|
||||
@ -241,7 +241,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
return base.IsVisible(user);
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -452,7 +452,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (newItems.Count > 0)
|
||||
{
|
||||
LibraryManager.CreateOrUpdateItems(newItems, this, cancellationToken);
|
||||
LibraryManager.CreateItems(newItems, this, cancellationToken);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -1202,6 +1202,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.Is4K.HasValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (request.IsHD.HasValue)
|
||||
{
|
||||
return false;
|
||||
|
@ -145,14 +145,14 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
return GetItemLookupInfo<BoxSetInfo>();
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
if (IsLegacyBoxSet)
|
||||
{
|
||||
return base.IsVisible(user);
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
if (base.IsVisible(user))
|
||||
if (base.IsVisible(user, skipAllowedTagsCheck))
|
||||
{
|
||||
if (LinkedChildren.Length == 0)
|
||||
{
|
||||
|
@ -258,7 +258,7 @@ namespace MediaBrowser.Controller.Library
|
||||
/// <param name="items">Items to create.</param>
|
||||
/// <param name="parent">Parent of new items.</param>
|
||||
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
|
||||
void CreateOrUpdateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken);
|
||||
void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the item.
|
||||
|
@ -1,6 +1,5 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Jellyfin.Data.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Net
|
||||
@ -20,31 +19,31 @@ namespace MediaBrowser.Controller.Net
|
||||
/// Gets or sets the device identifier.
|
||||
/// </summary>
|
||||
/// <value>The device identifier.</value>
|
||||
public string DeviceId { get; set; }
|
||||
public string? DeviceId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the device.
|
||||
/// </summary>
|
||||
/// <value>The device.</value>
|
||||
public string Device { get; set; }
|
||||
public string? Device { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the client.
|
||||
/// </summary>
|
||||
/// <value>The client.</value>
|
||||
public string Client { get; set; }
|
||||
public string? Client { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version.
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
public string? Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the token.
|
||||
/// </summary>
|
||||
/// <value>The token.</value>
|
||||
public string Token { get; set; }
|
||||
public string? Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the authorization is from an api key.
|
||||
@ -54,7 +53,7 @@ namespace MediaBrowser.Controller.Net
|
||||
/// <summary>
|
||||
/// Gets or sets the user making the request.
|
||||
/// </summary>
|
||||
public User User { get; set; }
|
||||
public User? User { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the token is authenticated.
|
||||
@ -62,8 +61,9 @@ namespace MediaBrowser.Controller.Net
|
||||
public bool IsAuthenticated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the request has a token.
|
||||
/// Gets a value indicating whether the request has a token.
|
||||
/// </summary>
|
||||
public bool HasToken { get; set; }
|
||||
[MemberNotNullWhen(true, nameof(Token))]
|
||||
public bool HasToken => !string.IsNullOrWhiteSpace(Token);
|
||||
}
|
||||
}
|
||||
|
@ -227,11 +227,11 @@ namespace MediaBrowser.Controller.Playlists
|
||||
return [item];
|
||||
}
|
||||
|
||||
public override bool IsVisible(User user)
|
||||
public override bool IsVisible(User user, bool skipAllowedTagsCheck = false)
|
||||
{
|
||||
if (!IsSharedItem)
|
||||
{
|
||||
return base.IsVisible(user);
|
||||
return base.IsVisible(user, skipAllowedTagsCheck);
|
||||
}
|
||||
|
||||
if (OpenAccess)
|
||||
|
@ -122,7 +122,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_jsonSerializerOptions = new JsonSerializerOptions(JsonDefaults.Options);
|
||||
_jsonSerializerOptions.Converters.Add(new JsonBoolStringConverter());
|
||||
|
||||
var semaphoreCount = 2 * Environment.ProcessorCount;
|
||||
// Although the type is not nullable, this might still be null during unit tests
|
||||
var semaphoreCount = serverConfig.Configuration?.ParallelImageEncodingLimit ?? 0;
|
||||
if (semaphoreCount < 1)
|
||||
{
|
||||
semaphoreCount = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
_thumbnailResourcePool = new(semaphoreCount);
|
||||
}
|
||||
|
||||
|
@ -15,13 +15,13 @@ public class ClientCapabilitiesDto
|
||||
/// <summary>
|
||||
/// Gets or sets the list of playable media types.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<MediaType> PlayableMediaTypes { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the list of supported commands.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonCommaDelimitedArrayConverterFactory))]
|
||||
[JsonConverter(typeof(JsonCommaDelimitedCollectionConverterFactory))]
|
||||
public IReadOnlyList<GeneralCommandType> SupportedCommands { get; set; } = [];
|
||||
|
||||
/// <summary>
|
||||
|
@ -500,7 +500,7 @@ namespace MediaBrowser.Model.Entities
|
||||
/// Gets or sets a value indicating whether this instance is for the hearing impaired.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is for the hearing impaired; otherwise, <c>false</c>.</value>
|
||||
public bool? IsHearingImpaired { get; set; }
|
||||
public bool IsHearingImpaired { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the height.
|
||||
|
@ -84,6 +84,11 @@ namespace MediaBrowser.Model.Entities
|
||||
/// <summary>
|
||||
/// The TvMaze provider.
|
||||
/// </summary>
|
||||
TvMaze = 19
|
||||
TvMaze = 19,
|
||||
|
||||
/// <summary>
|
||||
/// The MusicBrainz recording provider.
|
||||
/// </summary>
|
||||
MusicBrainzRecording = 20,
|
||||
}
|
||||
}
|
||||
|
@ -71,6 +71,11 @@ namespace MediaBrowser.Model.Providers
|
||||
/// <summary>
|
||||
/// A book.
|
||||
/// </summary>
|
||||
Book = 13
|
||||
Book = 13,
|
||||
|
||||
/// <summary>
|
||||
/// A music recording.
|
||||
/// </summary>
|
||||
Recording = 14
|
||||
}
|
||||
}
|
||||
|
@ -1,47 +0,0 @@
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace MediaBrowser.Model.System
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the MAC address and port for wake-on-LAN functionality.
|
||||
/// </summary>
|
||||
public class WakeOnLanInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
||||
/// </summary>
|
||||
/// <param name="macAddress">The MAC address.</param>
|
||||
public WakeOnLanInfo(PhysicalAddress macAddress) : this(macAddress.ToString())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
||||
/// </summary>
|
||||
/// <param name="macAddress">The MAC address.</param>
|
||||
public WakeOnLanInfo(string macAddress) : this()
|
||||
{
|
||||
MacAddress = macAddress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="WakeOnLanInfo" /> class.
|
||||
/// </summary>
|
||||
public WakeOnLanInfo()
|
||||
{
|
||||
Port = 9;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the MAC address of the device.
|
||||
/// </summary>
|
||||
/// <value>The MAC address.</value>
|
||||
public string? MacAddress { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the wake-on-LAN port.
|
||||
/// </summary>
|
||||
/// <value>The wake-on-LAN port.</value>
|
||||
public int Port { get; set; }
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -551,10 +552,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
var mimetype = response.Content.Headers.ContentType?.MediaType;
|
||||
if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
|
||||
}
|
||||
|
||||
await _providerManager.SaveImage(
|
||||
item,
|
||||
stream,
|
||||
response.Content.Headers.ContentType?.MediaType,
|
||||
mimetype,
|
||||
type,
|
||||
null,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
@ -677,10 +684,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await using (stream.ConfigureAwait(false))
|
||||
{
|
||||
var mimetype = response.Content.Headers.ContentType?.MediaType;
|
||||
if (mimetype is null || mimetype.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
mimetype = MimeTypes.GetMimeType(response.RequestMessage.RequestUri.GetLeftPart(UriPartial.Path));
|
||||
}
|
||||
|
||||
await _providerManager.SaveImage(
|
||||
item,
|
||||
stream,
|
||||
response.Content.Headers.ContentType?.MediaType,
|
||||
mimetype,
|
||||
imageType,
|
||||
null,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
@ -1162,6 +1162,16 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
person.ImageUrl = personInSource.ImageUrl;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(personInSource.Role) && string.IsNullOrWhiteSpace(person.Role))
|
||||
{
|
||||
person.Role = personInSource.Role;
|
||||
}
|
||||
|
||||
if (personInSource.SortOrder.HasValue && !person.SortOrder.HasValue)
|
||||
{
|
||||
person.SortOrder = personInSource.SortOrder;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,27 +205,10 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
contentType = MediaTypeNames.Image.Png;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Deduce content type from file extension
|
||||
contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
|
||||
}
|
||||
|
||||
// Throw if we still can't determine the content type
|
||||
if (string.IsNullOrEmpty(contentType))
|
||||
{
|
||||
throw new HttpRequestException("Invalid image received: contentType not set.", null, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
// TVDb will sometimes serve a rubbish 404 html page with a 200 OK code, because reasons...
|
||||
if (contentType.Equals(MediaTypeNames.Text.Html, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new HttpRequestException("Invalid image received.", null, HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
// some iptv/epg providers don't correctly report media type, extract from url if no extension found
|
||||
if (string.IsNullOrWhiteSpace(MimeTypes.ToExtension(contentType)))
|
||||
// some providers don't correctly report media type, extract from url if no extension found
|
||||
if (contentType is null || contentType.Equals(MediaTypeNames.Application.Octet, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Strip query parameters from url to get actual path.
|
||||
contentType = MimeTypes.GetMimeType(new Uri(url).GetLeftPart(UriPartial.Path));
|
||||
@ -233,7 +216,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
|
||||
if (!contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new HttpRequestException($"Request returned {contentType} instead of an image type", null, HttpStatusCode.NotFound);
|
||||
throw new HttpRequestException($"Request returned '{contentType}' instead of an image type", null, HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
var responseBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
@ -20,6 +20,7 @@ using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using static Jellyfin.Extensions.StringExtensions;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
{
|
||||
@ -175,11 +176,15 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
_logger.LogWarning("File {File} only has ID3v1 tags, some fields may be truncated", audio.Path);
|
||||
}
|
||||
|
||||
track.Title = string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title;
|
||||
track.Album = string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album;
|
||||
track.Year ??= mediaInfo.ProductionYear;
|
||||
track.TrackNumber ??= mediaInfo.IndexNumber;
|
||||
track.DiscNumber ??= mediaInfo.ParentIndexNumber;
|
||||
// We should never use the property setter of the ATL.Track class.
|
||||
// That setter is meant for its own tag parser and external editor usage and will have unwanted side effects
|
||||
// For example, setting the Year property will also set the Date property, which is not what we want here.
|
||||
// To properly handle fallback values, we make a clone of those fields when valid.
|
||||
var trackTitle = (string.IsNullOrEmpty(track.Title) ? mediaInfo.Name : track.Title).Trim();
|
||||
var trackAlbum = (string.IsNullOrEmpty(track.Album) ? mediaInfo.Album : track.Album).Trim();
|
||||
var trackYear = track.Year is null or 0 ? mediaInfo.ProductionYear : track.Year;
|
||||
var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
|
||||
var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
|
||||
|
||||
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
|
||||
{
|
||||
@ -276,22 +281,22 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(track.Title))
|
||||
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(trackTitle))
|
||||
{
|
||||
audio.Name = track.Title;
|
||||
audio.Name = trackTitle;
|
||||
}
|
||||
|
||||
if (options.ReplaceAllMetadata)
|
||||
{
|
||||
audio.Album = track.Album.Trim();
|
||||
audio.IndexNumber = track.TrackNumber;
|
||||
audio.ParentIndexNumber = track.DiscNumber;
|
||||
audio.Album = trackAlbum;
|
||||
audio.IndexNumber = trackTrackNumber;
|
||||
audio.ParentIndexNumber = trackDiscNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
audio.Album ??= track.Album.Trim();
|
||||
audio.IndexNumber ??= track.TrackNumber;
|
||||
audio.ParentIndexNumber ??= track.DiscNumber;
|
||||
audio.Album ??= trackAlbum;
|
||||
audio.IndexNumber ??= trackTrackNumber;
|
||||
audio.ParentIndexNumber ??= trackDiscNumber;
|
||||
}
|
||||
|
||||
if (track.Date.HasValue)
|
||||
@ -299,11 +304,12 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
audio.PremiereDate = track.Date;
|
||||
}
|
||||
|
||||
if (track.Year.HasValue)
|
||||
if (trackYear.HasValue)
|
||||
{
|
||||
var year = track.Year.Value;
|
||||
var year = trackYear.Value;
|
||||
audio.ProductionYear = year;
|
||||
|
||||
// ATL library handles such fallback this with its own internal logic, but we also need to handle it here for the ffprobe fallbacks.
|
||||
if (!audio.PremiereDate.HasValue)
|
||||
{
|
||||
try
|
||||
@ -312,7 +318,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
catch (ArgumentOutOfRangeException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, track.Year);
|
||||
_logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, trackYear);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -403,6 +409,24 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
}
|
||||
}
|
||||
|
||||
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _))
|
||||
{
|
||||
if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId)
|
||||
|| track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId))
|
||||
&& !string.IsNullOrEmpty(recordingMbId))
|
||||
{
|
||||
audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId);
|
||||
}
|
||||
else if (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
|
||||
{
|
||||
// If tagged with MB Picard, the format is 'http://musicbrainz.org\0<recording MBID>'
|
||||
if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, ufIdValue.AsSpan().RightPart('\0').ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save extracted lyrics if they exist,
|
||||
// and if the audio doesn't yet have lyrics.
|
||||
var lyrics = track.Lyrics.SynchronizedLyrics.Count > 0 ? track.Lyrics.FormatSynchToLRC() : track.Lyrics.UnsynchronizedLyrics;
|
||||
|
@ -119,9 +119,9 @@ namespace MediaBrowser.Providers.MediaInfo
|
||||
|| (mediaStream.Type == MediaStreamType.Subtitle && _type == DlnaProfileType.Subtitle))
|
||||
{
|
||||
mediaStream.Index = startIndex++;
|
||||
mediaStream.IsDefault = pathInfo.IsDefault || mediaStream.IsDefault;
|
||||
mediaStream.IsDefault = pathInfo.IsDefault;
|
||||
mediaStream.IsForced = pathInfo.IsForced || mediaStream.IsForced;
|
||||
mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired.GetValueOrDefault();
|
||||
mediaStream.IsHearingImpaired = pathInfo.IsHearingImpaired || mediaStream.IsHearingImpaired;
|
||||
|
||||
mediaStreams.Add(MergeMetadata(mediaStream, pathInfo));
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
|
||||
/// <summary>
|
||||
/// MusicBrainz recording id.
|
||||
/// </summary>
|
||||
public class MusicBrainzRecordingId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzRecording.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Recording;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance!.Configuration.Server + "/recording/{0}";
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Supports(IHasProviderIds item) => item is Audio;
|
||||
}
|
@ -55,13 +55,12 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
||||
|
||||
if (info.SeriesProviderIds.TryGetValue(MetadataProvider.Imdb.ToString(), out string? seriesImdbId)
|
||||
&& !string.IsNullOrEmpty(seriesImdbId)
|
||||
&& info.IndexNumber.HasValue
|
||||
&& info.ParentIndexNumber.HasValue)
|
||||
&& info.IndexNumber.HasValue)
|
||||
{
|
||||
result.HasMetadata = await _omdbProvider.FetchEpisodeData(
|
||||
result,
|
||||
info.IndexNumber.Value,
|
||||
info.ParentIndexNumber.Value,
|
||||
info.ParentIndexNumber ?? 1,
|
||||
info.GetProviderId(MetadataProvider.Imdb),
|
||||
seriesImdbId,
|
||||
info.MetadataLanguage,
|
||||
|
@ -63,10 +63,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
|
||||
var seasonNumber = episode.ParentIndexNumber;
|
||||
var seasonNumber = episode.ParentIndexNumber ?? 1;
|
||||
var episodeNumber = episode.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
if (!episodeNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteImageInfo>();
|
||||
}
|
||||
@ -75,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
|
||||
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
|
||||
var episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var stills = episodeResult?.Images?.Stills;
|
||||
|
@ -47,7 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(EpisodeInfo searchInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
// The search query must either provide an episode number or date
|
||||
if (!searchInfo.IndexNumber.HasValue || !searchInfo.ParentIndexNumber.HasValue)
|
||||
if (!searchInfo.IndexNumber.HasValue)
|
||||
{
|
||||
return Enumerable.Empty<RemoteSearchResult>();
|
||||
}
|
||||
@ -96,10 +96,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
return metadataResult;
|
||||
}
|
||||
|
||||
var seasonNumber = info.ParentIndexNumber;
|
||||
var seasonNumber = info.ParentIndexNumber ?? 1;
|
||||
var episodeNumber = info.IndexNumber;
|
||||
|
||||
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
|
||||
if (!episodeNumber.HasValue)
|
||||
{
|
||||
return metadataResult;
|
||||
}
|
||||
@ -112,7 +112,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
List<TvEpisode>? result = null;
|
||||
for (int? episode = startindex; episode <= endindex; episode++)
|
||||
{
|
||||
var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
|
||||
var episodeInfo = await _tmdbClientManager.GetEpisodeAsync(seriesTmdbId, seasonNumber, episode.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken).ConfigureAwait(false);
|
||||
if (episodeInfo is not null)
|
||||
{
|
||||
(result ??= new List<TvEpisode>()).Add(episodeInfo);
|
||||
@ -156,7 +156,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
else
|
||||
{
|
||||
episodeResult = await _tmdbClientManager
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.GetEpisodeAsync(seriesTmdbId, seasonNumber, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ public sealed class ImageProcessor : IImageProcessor, IDisposable
|
||||
var semaphoreCount = config.Configuration.ParallelImageEncodingLimit;
|
||||
if (semaphoreCount < 1)
|
||||
{
|
||||
semaphoreCount = 2 * Environment.ProcessorCount;
|
||||
semaphoreCount = Environment.ProcessorCount;
|
||||
}
|
||||
|
||||
_parallelEncodingLimit = new(semaphoreCount);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user