mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-24 02:02:29 -04:00
Merge changes
This commit is contained in:
commit
f26fc7dfb2
@ -259,7 +259,7 @@ jobs:
|
||||
publishFeedCredentials: 'NugetOrg'
|
||||
allowPackageConflicts: true # This ignores an error if the version already exists
|
||||
|
||||
- task: NuGetAuthenticate@0
|
||||
- task: NuGetAuthenticate@1
|
||||
displayName: 'Authenticate to unstable Nuget feed'
|
||||
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||
|
||||
|
28
.devcontainer/Dev - Server Ffmpeg/devcontainer.json
Normal file
28
.devcontainer/Dev - Server Ffmpeg/devcontainer.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Development Jellyfin Server - FFmpeg",
|
||||
"image":"mcr.microsoft.com/devcontainers/dotnet:8.0-jammy",
|
||||
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate
|
||||
"postStartCommand": "dotnet restore; dotnet workload update; dotnet dev-certs https --trust; sudo bash \"./.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh\"",
|
||||
// reads the extensions list and installs them
|
||||
"postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/dotnet:2": {
|
||||
"version": "none",
|
||||
"dotnetRuntimeVersions": "8.0",
|
||||
"aspNetCoreRuntimeVersions": "8.0"
|
||||
},
|
||||
"ghcr.io/devcontainers-contrib/features/apt-packages:1": {
|
||||
"preserve_apt_list": false,
|
||||
"packages": ["libfontconfig1"]
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"dockerDashComposeVersion": "v2"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/github-cli:1": {},
|
||||
"ghcr.io/eitsupi/devcontainer-features/jq-likes:2": {}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"memory": "8gb",
|
||||
"cpus": 4
|
||||
}
|
||||
}
|
32
.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh
Normal file
32
.devcontainer/Dev - Server Ffmpeg/install-ffmpeg.sh
Normal file
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
## configure the following for a manuall install of a specific version from the repo
|
||||
|
||||
# wget https://repo.jellyfin.org/releases/server/ubuntu/versions/jellyfin-ffmpeg/6.0.1-1/jellyfin-ffmpeg6_6.0.1-1-jammy_amd64.deb -O ffmpeg.deb
|
||||
|
||||
# sudo apt update
|
||||
# sudo apt install -f ./ffmpeg.deb -y
|
||||
# rm ffmpeg.deb
|
||||
|
||||
|
||||
## Add the jellyfin repo
|
||||
sudo apt install curl gnupg -y
|
||||
sudo apt-get install software-properties-common -y
|
||||
sudo add-apt-repository universe -y
|
||||
|
||||
sudo mkdir -p /etc/apt/keyrings
|
||||
curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/jellyfin.gpg
|
||||
export VERSION_OS="$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release )"
|
||||
export VERSION_CODENAME="$( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release )"
|
||||
export DPKG_ARCHITECTURE="$( dpkg --print-architecture )"
|
||||
cat <<EOF | sudo tee /etc/apt/sources.list.d/jellyfin.sources
|
||||
Types: deb
|
||||
URIs: https://repo.jellyfin.org/${VERSION_OS}
|
||||
Suites: ${VERSION_CODENAME}
|
||||
Components: main
|
||||
Architectures: ${DPKG_ARCHITECTURE}
|
||||
Signed-By: /etc/apt/keyrings/jellyfin.gpg
|
||||
EOF
|
||||
|
||||
sudo apt update -y
|
||||
sudo apt install jellyfin-ffmpeg6 -y
|
6
.github/workflows/ci-codeql-analysis.yml
vendored
6
.github/workflows/ci-codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
||||
dotnet-version: '8.0.x'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
|
||||
uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: +security-extended
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
|
||||
uses: github/codeql-action/autobuild@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@e5f05b81d5b6ff8cfa111c80c22c5fd02a384118 # v3.23.0
|
||||
uses: github/codeql-action/analyze@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0
|
||||
|
10
.github/workflows/ci-openapi.yml
vendored
10
.github/workflows/ci-openapi.yml
vendored
@ -25,7 +25,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@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: openapi-head
|
||||
retention-days: 14
|
||||
@ -59,7 +59,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@1eb3cb2b3e0f29609092a73eb033bb759a334595 # v4.1.0
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: openapi-base
|
||||
retention-days: 14
|
||||
@ -105,14 +105,14 @@ jobs:
|
||||
body="${body//$'\r'/'%0D'}"
|
||||
echo ::set-output name=body::$body
|
||||
- name: Find difference comment
|
||||
uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2.4.0
|
||||
uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3.0.0
|
||||
id: find-comment
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
direction: last
|
||||
body-includes: openapi-diff-workflow-comment
|
||||
- name: Reply or edit difference comment (changed)
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
@ -127,7 +127,7 @@ jobs:
|
||||
|
||||
</details>
|
||||
- name: Edit difference comment (unchanged)
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
|
10
.github/workflows/commands.yml
vendored
10
.github/workflows/commands.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
comment-id: ${{ github.event.comment.id }}
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Notify as seen
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -58,7 +58,7 @@ jobs:
|
||||
|
||||
- name: Notify as running
|
||||
id: comment_running
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -93,7 +93,7 @@ jobs:
|
||||
exit ${retcode}
|
||||
|
||||
- name: Notify with result success
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null && success() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
@ -108,7 +108,7 @@ jobs:
|
||||
reactions: hooray
|
||||
|
||||
- name: Notify with result failure
|
||||
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
|
||||
if: ${{ github.event.comment != null && failure() }}
|
||||
with:
|
||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||
|
2
.vscode/extensions.json
vendored
2
.vscode/extensions.json
vendored
@ -2,7 +2,7 @@
|
||||
"recommendations": [
|
||||
"ms-dotnettools.csharp",
|
||||
"editorconfig.editorconfig",
|
||||
"GitHub.vscode-github-actions",
|
||||
"github.vscode-github-actions",
|
||||
"ms-dotnettools.vscode-dotnet-runtime",
|
||||
"ms-dotnettools.csdevkit"
|
||||
],
|
||||
|
12
.vscode/launch.json
vendored
12
.vscode/launch.json
vendored
@ -29,6 +29,18 @@
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": "ghcs .NET Launch (nowebclient, ffmpeg)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceFolder}/Jellyfin.Server/bin/Debug/net8.0/jellyfin.dll",
|
||||
"args": ["--nowebclient", "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"],
|
||||
"cwd": "${workspaceFolder}/Jellyfin.Server",
|
||||
"console": "internalConsole",
|
||||
"stopAtEntry": false,
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
},
|
||||
{
|
||||
"name": ".NET Attach",
|
||||
"type": "coreclr",
|
||||
|
@ -4,6 +4,7 @@
|
||||
- [97carmine](https://github.com/97carmine)
|
||||
- [Abbe98](https://github.com/Abbe98)
|
||||
- [agrenott](https://github.com/agrenott)
|
||||
- [alltilla](https://github.com/alltilla)
|
||||
- [AndreCarvalho](https://github.com/AndreCarvalho)
|
||||
- [anthonylavado](https://github.com/anthonylavado)
|
||||
- [Artiume](https://github.com/Artiume)
|
||||
@ -173,9 +174,11 @@
|
||||
- [tallbl0nde](https://github.com/tallbl0nde)
|
||||
- [sleepycatcoding](https://github.com/sleepycatcoding)
|
||||
- [scampower3](https://github.com/scampower3)
|
||||
- [Chris-Codes-It] (https://github.com/Chris-Codes-It)
|
||||
- [Chris-Codes-It](https://github.com/Chris-Codes-It)
|
||||
- [Pithaya](https://github.com/Pithaya)
|
||||
- [Çağrı Sakaoğlu](https://github.com/ilovepilav)
|
||||
_ [Barasingha](https://github.com/MaVdbussche)
|
||||
- [Gauvino](https://github.com/Gauvino)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
</PropertyGroup>
|
||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||
<ItemGroup Label="Package Dependencies">
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="6.3.0" />
|
||||
<PackageVersion Include="AsyncKeyedLock" Version="6.3.4" />
|
||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
|
||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
|
||||
<PackageVersion Include="AutoFixture" Version="4.18.1" />
|
||||
@ -88,4 +88,4 @@
|
||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
<PackageVersion Include="xunit" Version="2.6.5" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
68
Dockerfile
68
Dockerfile
@ -8,72 +8,68 @@ FROM node:20-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& apk del curl \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& npm run build:production \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM debian:stable-slim as app
|
||||
FROM debian:bookworm-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_VISIBLE_DEVICES="all"
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
ARG GMMLIB_VERSION=22.0.2
|
||||
ARG IGC_VERSION=1.0.10395
|
||||
ARG NEO_VERSION=22.08.22549
|
||||
ARG LEVEL_ZERO_VERSION=1.3.22549
|
||||
ENV JELLYFIN_DATA_DIR=/config
|
||||
ENV JELLYFIN_CACHE_DIR=/cache
|
||||
|
||||
# https://github.com/intel/compute-runtime/releases
|
||||
ARG GMMLIB_VERSION=22.3.11.ci17757293
|
||||
ARG IGC_VERSION=1.0.15136.22
|
||||
ARG NEO_VERSION=23.39.27427.23
|
||||
ARG LEVEL_ZERO_VERSION=1.3.27427.23
|
||||
|
||||
# Install dependencies:
|
||||
# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
|
||||
# curl: healthcheck
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget curl \
|
||||
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
|
||||
&& curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
|
||||
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
mesa-va-drivers \
|
||||
jellyfin-ffmpeg5 \
|
||||
openssl \
|
||||
locales \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y mesa-va-drivers jellyfin-ffmpeg6 openssl locales \
|
||||
# Intel VAAPI Tone mapping dependencies:
|
||||
# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
|
||||
# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
|
||||
&& mkdir intel-compute-runtime \
|
||||
&& cd intel-compute-runtime \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
|
||||
&& wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
|
||||
&& curl -LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
|
||||
-LO https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
|
||||
-LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
|
||||
-LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl-icd_${NEO_VERSION}_amd64.deb \
|
||||
-LO https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/libigdgmm12_${GMMLIB_VERSION}_amd64.deb \
|
||||
&& dpkg -i *.deb \
|
||||
&& cd .. \
|
||||
&& rm -rf intel-compute-runtime \
|
||||
&& apt-get remove gnupg wget -y \
|
||||
&& apt-get remove gnupg -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# because of changes in docker and systemd we need to not build in parallel at the moment
|
||||
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
|
||||
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
|
||||
@ -83,11 +79,9 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
|
||||
ENTRYPOINT [ "./jellyfin/jellyfin", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
|
@ -4,64 +4,58 @@
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=8.0
|
||||
|
||||
|
||||
FROM node:20-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& apk del curl \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& npm run build:production \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-arm as qemu
|
||||
FROM arm32v7/debian:stable-slim as app
|
||||
FROM arm32v7/debian:bookworm-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_VISIBLE_DEVICES="all"
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
ENV JELLYFIN_DATA_DIR=/config
|
||||
ENV JELLYFIN_CACHE_DIR=/cache
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-arm-static /usr/bin
|
||||
|
||||
# curl: setup & healthcheck
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl && \
|
||||
curl -ks https://repo.jellyfin.org/debian/jellyfin_team.gpg.key | apt-key add - && \
|
||||
curl -ks https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | apt-key add - && \
|
||||
echo 'deb [arch=armhf] https://repo.jellyfin.org/debian buster main' > /etc/apt/sources.list.d/jellyfin.list && \
|
||||
echo "deb http://ppa.launchpad.net/ubuntu-raspi2/ppa/ubuntu bionic main">> /etc/apt/sources.list.d/raspbins.list && \
|
||||
apt-get update && \
|
||||
apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
jellyfin-ffmpeg \
|
||||
libssl-dev \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
vainfo \
|
||||
libva2 \
|
||||
locales \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
|
||||
&& curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
|
||||
&& curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
|
||||
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
jellyfin-ffmpeg6 libssl-dev libfontconfig1 \
|
||||
libfreetype6 vainfo libva2 locales \
|
||||
&& apt-get remove gnupg -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
@ -72,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
|
||||
ENTRYPOINT [ "/jellyfin/jellyfin", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
|
@ -4,58 +4,58 @@
|
||||
# https://github.com/multiarch/qemu-user-static#binfmt_misc-register
|
||||
ARG DOTNET_VERSION=8.0
|
||||
|
||||
|
||||
FROM node:20-alpine as web-builder
|
||||
ARG JELLYFIN_WEB_VERSION=master
|
||||
RUN apk add curl git zlib zlib-dev autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool make gcc musl-dev nasm python3 \
|
||||
&& curl -L https://github.com/jellyfin/jellyfin-web/archive/${JELLYFIN_WEB_VERSION}.tar.gz | tar zxf - \
|
||||
&& apk del curl \
|
||||
&& cd jellyfin-web-* \
|
||||
&& npm ci --no-audit --unsafe-perm \
|
||||
&& npm run build:production \
|
||||
&& mv dist /dist
|
||||
|
||||
FROM multiarch/qemu-user-static:x86_64-aarch64 as qemu
|
||||
FROM arm64v8/debian:stable-slim as app
|
||||
FROM arm64v8/debian:bookworm-slim as app
|
||||
|
||||
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
|
||||
ARG DEBIAN_FRONTEND="noninteractive"
|
||||
# http://stackoverflow.com/questions/48162574/ddg#49462622
|
||||
ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
|
||||
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
|
||||
ENV NVIDIA_VISIBLE_DEVICES="all"
|
||||
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
|
||||
|
||||
ENV JELLYFIN_DATA_DIR=/config
|
||||
ENV JELLYFIN_CACHE_DIR=/cache
|
||||
|
||||
COPY --from=qemu /usr/bin/qemu-aarch64-static /usr/bin
|
||||
|
||||
# curl: healcheck
|
||||
RUN apt-get update && apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
ffmpeg \
|
||||
libssl-dev \
|
||||
ca-certificates \
|
||||
libfontconfig1 \
|
||||
libfreetype6 \
|
||||
libomxil-bellagio0 \
|
||||
libomxil-bellagio-bin \
|
||||
locales \
|
||||
curl \
|
||||
RUN apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg curl \
|
||||
&& curl -fsSL https://repo.jellyfin.org/jellyfin_team.gpg.key | gpg --dearmor -o /etc/apt/trusted.gpg.d/debian-jellyfin.gpg \
|
||||
&& curl -fsSL https://keyserver.ubuntu.com/pks/lookup?op=get\&search=0x6587ffd6536b8826e88a62547876ae518cbcf2f2 | gpg --dearmor -o /etc/apt/trusted.gpg.d/ubuntu-jellyfin.gpg \
|
||||
&& echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
|
||||
&& apt-get update \
|
||||
&& apt-get install --no-install-recommends --no-install-suggests -y \
|
||||
jellyfin-ffmpeg6 locales libssl-dev libfontconfig1 \
|
||||
libfreetype6 libomxil-bellagio0 libomxil-bellagio-bin \
|
||||
&& apt-get remove gnupg -y \
|
||||
&& apt-get clean autoclean -y \
|
||||
&& apt-get autoremove -y \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /cache /config /media \
|
||||
&& chmod 777 /cache /config /media \
|
||||
&& mkdir -p ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& chmod 777 ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR} \
|
||||
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
|
||||
|
||||
# ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
ENV LANGUAGE en_US:en
|
||||
ENV LC_ALL=en_US.UTF-8
|
||||
ENV LANG=en_US.UTF-8
|
||||
ENV LANGUAGE=en_US:en
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION} as builder
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
# Discard objs - may cause failures if exists
|
||||
RUN find . -type d -name obj | xargs -r rm -r
|
||||
# Build
|
||||
|
||||
RUN dotnet publish Jellyfin.Server --configuration Release --output="/jellyfin" --self-contained --runtime linux-arm64 -p:DebugSymbols=false -p:DebugType=none
|
||||
|
||||
FROM app
|
||||
@ -66,11 +66,9 @@ COPY --from=builder /jellyfin /jellyfin
|
||||
COPY --from=web-builder /dist /jellyfin/jellyfin-web
|
||||
|
||||
EXPOSE 8096
|
||||
VOLUME /cache /config
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/bin/ffmpeg"]
|
||||
VOLUME ${JELLYFIN_DATA_DIR} ${JELLYFIN_CACHE_DIR}
|
||||
ENTRYPOINT [ "/jellyfin/jellyfin", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg" ]
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=10s --retries=3 \
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
CMD curl -Lk -fsS "${HEALTHCHECK_URL}" || exit 1
|
||||
|
@ -695,7 +695,7 @@ namespace Emby.Server.Implementations
|
||||
GetExports<IMetadataSaver>(),
|
||||
GetExports<IExternalId>());
|
||||
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<ITunerHost>(), GetExports<IListingsProvider>());
|
||||
Resolve<ILiveTvManager>().AddParts(GetExports<ILiveTvService>(), GetExports<IListingsProvider>());
|
||||
|
||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||
}
|
||||
|
@ -699,7 +699,7 @@ namespace Emby.Server.Implementations.Data
|
||||
saveItemStatement.TryBindNull("@EndDate");
|
||||
}
|
||||
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.Equals(default) ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
|
||||
saveItemStatement.TryBind("@ChannelId", item.ChannelId.IsEmpty() ? null : item.ChannelId.ToString("N", CultureInfo.InvariantCulture));
|
||||
|
||||
if (item is IHasProgramAttributes hasProgramAttributes)
|
||||
{
|
||||
@ -729,7 +729,7 @@ namespace Emby.Server.Implementations.Data
|
||||
saveItemStatement.TryBind("@ProductionYear", item.ProductionYear);
|
||||
|
||||
var parentId = item.ParentId;
|
||||
if (parentId.Equals(default))
|
||||
if (parentId.IsEmpty())
|
||||
{
|
||||
saveItemStatement.TryBindNull("@ParentId");
|
||||
}
|
||||
@ -925,7 +925,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
saveItemStatement.TryBind("@SeasonName", episode.SeasonName);
|
||||
|
||||
var nullableSeasonId = episode.SeasonId.Equals(default) ? (Guid?)null : episode.SeasonId;
|
||||
var nullableSeasonId = episode.SeasonId.IsEmpty() ? (Guid?)null : episode.SeasonId;
|
||||
|
||||
saveItemStatement.TryBind("@SeasonId", nullableSeasonId);
|
||||
}
|
||||
@ -937,7 +937,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (item is IHasSeries hasSeries)
|
||||
{
|
||||
var nullableSeriesId = hasSeries.SeriesId.Equals(default) ? (Guid?)null : hasSeries.SeriesId;
|
||||
var nullableSeriesId = hasSeries.SeriesId.IsEmpty() ? (Guid?)null : hasSeries.SeriesId;
|
||||
|
||||
saveItemStatement.TryBind("@SeriesId", nullableSeriesId);
|
||||
saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey);
|
||||
@ -1010,7 +1010,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
|
||||
Guid ownerId = item.OwnerId;
|
||||
if (ownerId.Equals(default))
|
||||
if (ownerId.IsEmpty())
|
||||
{
|
||||
saveItemStatement.TryBindNull("@OwnerId");
|
||||
}
|
||||
@ -1266,7 +1266,7 @@ namespace Emby.Server.Implementations.Data
|
||||
/// <exception cref="ArgumentException"><paramr name="id"/> is <seealso cref="Guid.Empty"/>.</exception>
|
||||
public BaseItem RetrieveItem(Guid id)
|
||||
{
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
@ -1970,7 +1970,7 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@ -3230,7 +3230,7 @@ namespace Emby.Server.Implementations.Data
|
||||
whereClauses.Add($"ChannelId in ({inClause})");
|
||||
}
|
||||
|
||||
if (!query.ParentId.Equals(default))
|
||||
if (!query.ParentId.IsEmpty())
|
||||
{
|
||||
whereClauses.Add("ParentId=@ParentId");
|
||||
statement?.TryBind("@ParentId", query.ParentId);
|
||||
@ -4452,7 +4452,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@ -4583,13 +4583,13 @@ AND Type = @InternalPersonType)");
|
||||
statement?.TryBind("@UserId", query.User.InternalId);
|
||||
}
|
||||
|
||||
if (!query.ItemId.Equals(default))
|
||||
if (!query.ItemId.IsEmpty())
|
||||
{
|
||||
whereClauses.Add("ItemId=@ItemId");
|
||||
statement?.TryBind("@ItemId", query.ItemId);
|
||||
}
|
||||
|
||||
if (!query.AppearsInItemId.Equals(default))
|
||||
if (!query.AppearsInItemId.IsEmpty())
|
||||
{
|
||||
whereClauses.Add("p.Name in (Select Name from People where ItemId=@AppearsInItemId)");
|
||||
statement?.TryBind("@AppearsInItemId", query.AppearsInItemId);
|
||||
@ -4640,7 +4640,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement)
|
||||
{
|
||||
if (itemId.Equals(default))
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@ -5156,7 +5156,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db)
|
||||
{
|
||||
if (itemId.Equals(default))
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@ -5228,7 +5228,7 @@ AND Type = @InternalPersonType)");
|
||||
|
||||
public void UpdatePeople(Guid itemId, List<PersonInfo> people)
|
||||
{
|
||||
if (itemId.Equals(default))
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(itemId));
|
||||
}
|
||||
@ -5378,7 +5378,7 @@ AND Type = @InternalPersonType)");
|
||||
{
|
||||
CheckDisposed();
|
||||
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
@ -5758,7 +5758,7 @@ AND Type = @InternalPersonType)");
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
CheckDisposed();
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty.", nameof(id));
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -241,7 +242,7 @@ public sealed class LibraryChangedNotifier : IServerEntryPoint
|
||||
{
|
||||
var userIds = _sessionManager.Sessions
|
||||
.Select(i => i.UserId)
|
||||
.Where(i => !i.Equals(default))
|
||||
.Where(i => !i.IsEmpty())
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
|
@ -732,7 +732,7 @@ namespace Emby.Server.Implementations.Library
|
||||
Path = path
|
||||
};
|
||||
|
||||
if (folder.Id.Equals(default))
|
||||
if (folder.Id.IsEmpty())
|
||||
{
|
||||
if (string.IsNullOrEmpty(folder.Path))
|
||||
{
|
||||
@ -1219,7 +1219,7 @@ namespace Emby.Server.Implementations.Library
|
||||
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
|
||||
public BaseItem GetItemById(Guid id)
|
||||
{
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
@ -1241,7 +1241,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
|
||||
{
|
||||
if (query.Recursive && !query.ParentId.Equals(default))
|
||||
if (query.Recursive && !query.ParentId.IsEmpty())
|
||||
{
|
||||
var parent = GetItemById(query.ParentId);
|
||||
if (parent is not null)
|
||||
@ -1272,7 +1272,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public int GetCount(InternalItemsQuery query)
|
||||
{
|
||||
if (query.Recursive && !query.ParentId.Equals(default))
|
||||
if (query.Recursive && !query.ParentId.IsEmpty())
|
||||
{
|
||||
var parent = GetItemById(query.ParentId);
|
||||
if (parent is not null)
|
||||
@ -1430,7 +1430,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query)
|
||||
{
|
||||
if (query.Recursive && !query.ParentId.Equals(default))
|
||||
if (query.Recursive && !query.ParentId.IsEmpty())
|
||||
{
|
||||
var parent = GetItemById(query.ParentId);
|
||||
if (parent is not null)
|
||||
@ -1486,7 +1486,7 @@ namespace Emby.Server.Implementations.Library
|
||||
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
|
||||
{
|
||||
if (query.AncestorIds.Length == 0 &&
|
||||
query.ParentId.Equals(default) &&
|
||||
query.ParentId.IsEmpty() &&
|
||||
query.ChannelIds.Count == 0 &&
|
||||
query.TopParentIds.Length == 0 &&
|
||||
string.IsNullOrEmpty(query.AncestorWithPresentationUniqueKey) &&
|
||||
@ -1520,7 +1520,7 @@ namespace Emby.Server.Implementations.Library
|
||||
}
|
||||
|
||||
// Translate view into folders
|
||||
if (!view.DisplayParentId.Equals(default))
|
||||
if (!view.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(view.DisplayParentId);
|
||||
if (displayParent is not null)
|
||||
@ -1531,7 +1531,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return Array.Empty<Guid>();
|
||||
}
|
||||
|
||||
if (!view.ParentId.Equals(default))
|
||||
if (!view.ParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(view.ParentId);
|
||||
if (displayParent is not null)
|
||||
@ -2137,7 +2137,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return null;
|
||||
}
|
||||
|
||||
while (!item.ParentId.Equals(default))
|
||||
while (!item.ParentId.IsEmpty())
|
||||
{
|
||||
var parent = item.GetParent();
|
||||
if (parent is null || parent is AggregateFolder)
|
||||
@ -2215,7 +2215,7 @@ namespace Emby.Server.Implementations.Library
|
||||
CollectionType? viewType,
|
||||
string sortName)
|
||||
{
|
||||
var parentIdString = parentId.Equals(default)
|
||||
var parentIdString = parentId.IsEmpty()
|
||||
? null
|
||||
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
|
||||
@ -2251,7 +2251,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@ -2315,7 +2315,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@ -2345,7 +2345,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(name);
|
||||
|
||||
var parentIdString = parentId.Equals(default)
|
||||
var parentIdString = parentId.IsEmpty()
|
||||
? null
|
||||
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
|
||||
@ -2391,7 +2391,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var refresh = isNew || DateTime.UtcNow - item.DateLastRefreshed >= _viewRefreshInterval;
|
||||
|
||||
if (!refresh && !item.DisplayParentId.Equals(default))
|
||||
if (!refresh && !item.DisplayParentId.IsEmpty())
|
||||
{
|
||||
var displayParent = GetItemById(item.DisplayParentId);
|
||||
refresh = displayParent is not null && displayParent.DateLastSaved > item.DateLastRefreshed;
|
||||
@ -2419,7 +2419,7 @@ namespace Emby.Server.Implementations.Library
|
||||
return GetItemById(parentId.Value);
|
||||
}
|
||||
|
||||
if (userId.HasValue && !userId.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
return GetUserRootFolder();
|
||||
}
|
||||
|
@ -48,20 +48,23 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (!string.IsNullOrEmpty(cacheKey))
|
||||
{
|
||||
FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
||||
try
|
||||
{
|
||||
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
||||
|
||||
// _logger.LogDebug("Found cached media info");
|
||||
await using (jsonStream.ConfigureAwait(false))
|
||||
{
|
||||
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||
// _logger.LogDebug("Found cached media info");
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.LogDebug(ex, "Could not open cached media info");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deserializing mediainfo cache");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await jsonStream.DisposeAsync().ConfigureAwait(false);
|
||||
_logger.LogError(ex, "Error opening cached media info");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ using System.Threading.Tasks;
|
||||
using AsyncKeyedLock;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@ -519,10 +520,10 @@ namespace Emby.Server.Implementations.Library
|
||||
_logger.LogInformation("Live stream opened: {@MediaSource}", mediaSource);
|
||||
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);
|
||||
|
||||
if (!request.UserId.Equals(default))
|
||||
if (!request.UserId.IsEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(request.UserId);
|
||||
var item = request.ItemId.Equals(default)
|
||||
var item = request.ItemId.IsEmpty()
|
||||
? null
|
||||
: _libraryManager.GetItemById(request.ItemId);
|
||||
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user);
|
||||
|
@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@ -80,7 +81,7 @@ namespace Emby.Server.Implementations.Library
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
}).Where(i => !i.Equals(default)).ToArray();
|
||||
}).Where(i => !i.IsEmpty()).ToArray();
|
||||
|
||||
return GetInstantMixFromGenreIds(genreIds, user, dtoOptions);
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library
|
||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||
{
|
||||
User user = null;
|
||||
if (!query.UserId.Equals(default))
|
||||
if (!query.UserId.IsEmpty())
|
||||
{
|
||||
user = _userManager.GetUserById(query.UserId);
|
||||
}
|
||||
@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
if (searchQuery.IncludeItemTypes.Length == 1 && searchQuery.IncludeItemTypes[0] == BaseItemKind.MusicArtist)
|
||||
{
|
||||
if (!searchQuery.ParentId.Equals(default))
|
||||
if (!searchQuery.ParentId.IsEmpty())
|
||||
{
|
||||
searchQuery.AncestorIds = new[] { searchQuery.ParentId };
|
||||
searchQuery.ParentId = Guid.Empty;
|
||||
|
@ -8,6 +8,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@ -151,7 +152,7 @@ namespace Emby.Server.Implementations.Library
|
||||
var index = Array.IndexOf(orders, i.Id);
|
||||
if (index == -1
|
||||
&& i is UserView view
|
||||
&& !view.DisplayParentId.Equals(default))
|
||||
&& !view.DisplayParentId.IsEmpty())
|
||||
{
|
||||
index = Array.IndexOf(orders, view.DisplayParentId);
|
||||
}
|
||||
@ -253,7 +254,7 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
var parents = new List<BaseItem>();
|
||||
|
||||
if (!parentId.Equals(default))
|
||||
if (!parentId.IsEmpty())
|
||||
{
|
||||
var parentItem = _libraryManager.GetItemById(parentId);
|
||||
if (parentItem is Channel)
|
||||
|
@ -52,7 +52,7 @@
|
||||
"PluginUninstalledWithName": "{0} eemaldati",
|
||||
"PluginInstalledWithName": "{0} paigaldati",
|
||||
"Plugin": "Plugin",
|
||||
"Playlists": "Pleilistid",
|
||||
"Playlists": "Esitusloendid",
|
||||
"Photos": "Fotod",
|
||||
"NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes",
|
||||
"NotificationOptionVideoPlayback": "Video taasesitus algas",
|
||||
@ -123,5 +123,7 @@
|
||||
"External": "Väline",
|
||||
"HearingImpaired": "Kuulmispuudega",
|
||||
"TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.",
|
||||
"TaskKeyframeExtractor": "Võtmekaadri ekstraktor"
|
||||
"TaskKeyframeExtractor": "Võtmekaadri ekstraktor",
|
||||
"TaskRefreshTrickplayImages": "Loo eelvaate pildid",
|
||||
"TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud."
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
"HeaderFavoriteAlbums": "რჩეული ალბომები",
|
||||
"TasksApplicationCategory": "აპლიკაცია",
|
||||
"Albums": "ალბომები",
|
||||
"AppDeviceValues": "აპი: {0}, მოწყობილობა: {1}",
|
||||
"AppDeviceValues": "აპლიკაცია: {0}, მოწყობილობა: {1}",
|
||||
"Application": "აპლიკაცია",
|
||||
"Artists": "შემსრულებლები",
|
||||
"Artists": "არტისტი",
|
||||
"AuthenticationSucceededWithUserName": "{0} -ის ავთენტიკაცია წარმატებულია",
|
||||
"Books": "წიგნები",
|
||||
"Forced": "ძალით",
|
||||
|
1
Emby.Server.Implementations/Localization/Core/ky.json
Normal file
1
Emby.Server.Implementations/Localization/Core/ky.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
@ -124,5 +124,7 @@
|
||||
"External": "Luaran",
|
||||
"TaskOptimizeDatabase": "Optimumkan pangkalan data",
|
||||
"TaskKeyframeExtractor": "Ekstrak bingkai kunci",
|
||||
"TaskKeyframeExtractorDescription": "Ekstrak bingkai kunci dari fail video untuk membina HLS playlist yang lebih tepat. Tugas ini mungkin perlukan masa yang panjang."
|
||||
"TaskKeyframeExtractorDescription": "Ekstrak bingkai kunci dari fail video untuk membina HLS playlist yang lebih tepat. Tugas ini mungkin perlukan masa yang panjang.",
|
||||
"TaskRefreshTrickplayImagesDescription": "Jana gambar prebiu Trickplay untuk video dalam perpustakaan.",
|
||||
"TaskRefreshTrickplayImages": "Jana gambar Trickplay"
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"Artists": "Artister",
|
||||
"AuthenticationSucceededWithUserName": "{0} har logget inn",
|
||||
"Books": "Bøker",
|
||||
"CameraImageUploadedFrom": "Et nytt kamerabilde er lastet opp fra {0}",
|
||||
"CameraImageUploadedFrom": "Et nytt kamerabilde har blitt lastet opp fra {0}",
|
||||
"Channels": "Kanaler",
|
||||
"ChapterNameValue": "Kapittel {0}",
|
||||
"Collections": "Samlinger",
|
||||
@ -32,10 +32,10 @@
|
||||
"LabelIpAddressValue": "IP-adresse: {0}",
|
||||
"LabelRunningTimeValue": "Spilletid {0}",
|
||||
"Latest": "Siste",
|
||||
"MessageApplicationUpdated": "Jellyfin-tjeneren har blitt oppdatert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin-tjeneren ble oppdatert til {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Tjenerkonfigurasjonsseksjon {0} har blitt oppdatert",
|
||||
"MessageServerConfigurationUpdated": "Tjenerkonfigurasjon er oppdatert",
|
||||
"MessageApplicationUpdated": "Jellyfin-serveren har blitt oppdatert",
|
||||
"MessageApplicationUpdatedTo": "Jellyfin-serveren ble oppdatert til {0}",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurasjonsseksjon {0} har blitt oppdatert",
|
||||
"MessageServerConfigurationUpdated": "Serverkonfigurasjon har blitt oppdatert",
|
||||
"MixedContent": "Blandet innhold",
|
||||
"Movies": "Filmer",
|
||||
"Music": "Musikk",
|
||||
@ -43,19 +43,19 @@
|
||||
"NameInstallFailed": "Installasjonen av {0} mislyktes",
|
||||
"NameSeasonNumber": "Sesong {0}",
|
||||
"NameSeasonUnknown": "Ukjent sesong",
|
||||
"NewVersionIsAvailable": "En ny versjon av Jellyfin-tjeneren er tilgjengelig for nedlasting.",
|
||||
"NewVersionIsAvailable": "En ny versjon av Jellyfin Server er tilgjengelig for nedlasting.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "En programvareoppdatering er tilgjengelig",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Applikasjonsoppdatering installert",
|
||||
"NotificationOptionAudioPlayback": "Lydavspilling startet",
|
||||
"NotificationOptionAudioPlaybackStopped": "Lydavspilling stoppet",
|
||||
"NotificationOptionCameraImageUploaded": "Kamerabilde lastet opp",
|
||||
"NotificationOptionInstallationFailed": "Installasjonen feilet",
|
||||
"NotificationOptionInstallationFailed": "Installasjonsfeil",
|
||||
"NotificationOptionNewLibraryContent": "Nytt innhold lagt til",
|
||||
"NotificationOptionPluginError": "Programvareutvidelsesfeil",
|
||||
"NotificationOptionPluginInstalled": "Programvareutvidelse installert",
|
||||
"NotificationOptionPluginUninstalled": "Programvareutvidelse avinstallert",
|
||||
"NotificationOptionPluginUpdateInstalled": "Programvareutvidelsesoppdatering installert",
|
||||
"NotificationOptionServerRestartRequired": "Tjeneromstart er nødvendig",
|
||||
"NotificationOptionServerRestartRequired": "Serveromstart er nødvendig",
|
||||
"NotificationOptionTaskFailed": "Feil under utføring av planlagt oppgave",
|
||||
"NotificationOptionUserLockedOut": "Bruker er utestengt",
|
||||
"NotificationOptionVideoPlayback": "Videoavspilling startet",
|
||||
@ -70,9 +70,9 @@
|
||||
"ScheduledTaskFailedWithName": "{0} mislykkes",
|
||||
"ScheduledTaskStartedWithName": "{0} startet",
|
||||
"ServerNameNeedsToBeRestarted": "{0} må startes på nytt",
|
||||
"Shows": "Program",
|
||||
"Shows": "Serier",
|
||||
"Songs": "Sanger",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin-tjener laster. Prøv igjen snart.",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server laster. Prøv igjen snart.",
|
||||
"SubtitleDownloadFailureForItem": "En feil oppstå under nedlasting av undertekster for {0}",
|
||||
"SubtitleDownloadFailureFromForItem": "Kunne ikke laste ned undertekster fra {0} for {1}",
|
||||
"Sync": "Synkroniser",
|
||||
|
@ -124,5 +124,7 @@
|
||||
"TaskKeyframeExtractor": "Ekstraktor ključnih sličic",
|
||||
"External": "Zunanji",
|
||||
"TaskKeyframeExtractorDescription": "Iz video datoteke Izvleče ključne sličice, da ustvari bolj natančne sezname predvajanja HLS. Proces lahko traja dolgo časa.",
|
||||
"HearingImpaired": "Oslabljen sluh"
|
||||
"HearingImpaired": "Oslabljen sluh",
|
||||
"TaskRefreshTrickplayImages": "Ustvari Trickplay slike",
|
||||
"TaskRefreshTrickplayImagesDescription": "Ustvari trickplay predoglede za posnetke v omogočenih knjižnicah."
|
||||
}
|
||||
|
@ -43,7 +43,7 @@
|
||||
"NameInstallFailed": "{0} installationen misslyckades",
|
||||
"NameSeasonNumber": "Säsong {0}",
|
||||
"NameSeasonUnknown": "Okänd säsong",
|
||||
"NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig att hämta.",
|
||||
"NewVersionIsAvailable": "En ny version av Jellyfin Server är tillgänglig för nedladdning.",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Ny programversion tillgänglig",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Programuppdatering installerad",
|
||||
"NotificationOptionAudioPlayback": "Ljuduppspelning har påbörjats",
|
||||
@ -74,7 +74,7 @@
|
||||
"Songs": "Låtar",
|
||||
"StartupEmbyServerIsLoading": "Jellyfin Server arbetar. Pröva igen snart.",
|
||||
"SubtitleDownloadFailureForItem": "Nerladdning av undertexter för {0} misslyckades",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} för {1}",
|
||||
"SubtitleDownloadFailureFromForItem": "Undertexter kunde inte laddas ner från {0} till {1}",
|
||||
"Sync": "Synk",
|
||||
"System": "System",
|
||||
"TvShows": "TV-serier",
|
||||
|
3
Emby.Server.Implementations/Localization/Core/ur.json
Normal file
3
Emby.Server.Implementations/Localization/Core/ur.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Books": "کتابیں"
|
||||
}
|
@ -123,5 +123,7 @@
|
||||
"TaskKeyframeExtractor": "Trích Xuất Khung Hình",
|
||||
"TaskKeyframeExtractorDescription": "Trích xuất khung hình chính từ các tệp video để tạo danh sách phát HLS chính xác hơn. Tác vụ này có thể chạy trong một thời gian dài.",
|
||||
"External": "Bên ngoài",
|
||||
"HearingImpaired": "Khiếm Thính"
|
||||
"HearingImpaired": "Khiếm Thính",
|
||||
"TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay",
|
||||
"TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật."
|
||||
}
|
||||
|
@ -83,13 +83,13 @@
|
||||
"UserDeletedWithName": "用戶 {0} 已被移除",
|
||||
"UserDownloadingItemWithValues": "{0} 正在下載 {1}",
|
||||
"UserLockedOutWithName": "用戶 {0} 已被封鎖",
|
||||
"UserOfflineFromDevice": "{0} 從 {1} 斷開連接",
|
||||
"UserOfflineFromDevice": "{0} 終止了 {1} 的連接",
|
||||
"UserOnlineFromDevice": "{0} 從 {1} 連線",
|
||||
"UserPasswordChangedWithName": "{0} 的密碼已被變改",
|
||||
"UserPasswordChangedWithName": "{0} 的密碼已被更改",
|
||||
"UserPolicyUpdatedWithName": "使用條款已更新為 {0}",
|
||||
"UserStartedPlayingItemWithValues": "{0} 在 {2} 上播放 {1}",
|
||||
"UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 上播放 {1}",
|
||||
"ValueHasBeenAddedToLibrary": "已添加 {0} 到你的媒體庫",
|
||||
"UserStoppedPlayingItemWithValues": "{0} 停止在 {2} 上播放 {1}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} 已被加入至你的媒體庫",
|
||||
"ValueSpecialEpisodeName": "特典 - {0}",
|
||||
"VersionNumber": "版本 {0}",
|
||||
"TaskDownloadMissingSubtitles": "下載欠缺字幕",
|
||||
|
@ -11,6 +11,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@ -178,7 +179,7 @@ namespace Emby.Server.Implementations.Playlists
|
||||
|
||||
public Task AddToPlaylistAsync(Guid playlistId, IReadOnlyCollection<Guid> itemIds, Guid userId)
|
||||
{
|
||||
var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
|
||||
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
|
||||
|
||||
return AddToPlaylistInternal(playlistId, itemIds, user, new DtoOptions(false)
|
||||
{
|
||||
|
@ -115,9 +115,10 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
|
||||
List<LinkedChild>? itemsToRemove = null;
|
||||
foreach (var linkedChild in folder.LinkedChildren)
|
||||
{
|
||||
if (!File.Exists(folder.Path))
|
||||
var path = linkedChild.Path;
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
_logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, linkedChild.Path);
|
||||
_logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path);
|
||||
(itemsToRemove ??= new List<LinkedChild>()).Add(linkedChild);
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ namespace Emby.Server.Implementations.Session
|
||||
_logger);
|
||||
}
|
||||
|
||||
private void OnSessionEnded(SessionInfo info)
|
||||
private async ValueTask OnSessionEnded(SessionInfo info)
|
||||
{
|
||||
EventHelper.QueueEventIfNotNull(
|
||||
SessionEnded,
|
||||
@ -202,7 +202,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
_eventManager.Publish(new SessionEndedEventArgs(info));
|
||||
|
||||
info.Dispose();
|
||||
await info.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -301,12 +301,12 @@ namespace Emby.Server.Implementations.Session
|
||||
await _mediaSourceManager.CloseLiveStream(session.PlayState.LiveStreamId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
OnSessionEnded(session);
|
||||
await OnSessionEnded(session).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ReportSessionEnded(string sessionId)
|
||||
public async ValueTask ReportSessionEnded(string sessionId)
|
||||
{
|
||||
CheckDisposed();
|
||||
var session = GetSession(sessionId, false);
|
||||
@ -317,7 +317,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
_activeConnections.TryRemove(key, out _);
|
||||
|
||||
OnSessionEnded(session);
|
||||
await OnSessionEnded(session).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,7 +337,7 @@ namespace Emby.Server.Implementations.Session
|
||||
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null)
|
||||
if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null)
|
||||
{
|
||||
var current = session.NowPlayingItem;
|
||||
|
||||
@ -529,7 +529,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var users = new List<User>();
|
||||
|
||||
if (session.UserId.Equals(default))
|
||||
if (session.UserId.IsEmpty())
|
||||
{
|
||||
return users;
|
||||
}
|
||||
@ -690,7 +690,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var session = GetSession(info.SessionId);
|
||||
|
||||
var libraryItem = info.ItemId.Equals(default)
|
||||
var libraryItem = info.ItemId.IsEmpty()
|
||||
? null
|
||||
: GetNowPlayingItem(session, info.ItemId);
|
||||
|
||||
@ -784,7 +784,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var session = GetSession(info.SessionId);
|
||||
|
||||
var libraryItem = info.ItemId.Equals(default)
|
||||
var libraryItem = info.ItemId.IsEmpty()
|
||||
? null
|
||||
: GetNowPlayingItem(session, info.ItemId);
|
||||
|
||||
@ -923,7 +923,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
session.StopAutomaticProgress();
|
||||
|
||||
var libraryItem = info.ItemId.Equals(default)
|
||||
var libraryItem = info.ItemId.IsEmpty()
|
||||
? null
|
||||
: GetNowPlayingItem(session, info.ItemId);
|
||||
|
||||
@ -933,7 +933,7 @@ namespace Emby.Server.Implementations.Session
|
||||
info.MediaSourceId = info.ItemId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
if (!info.ItemId.Equals(default) && info.Item is null && libraryItem is not null)
|
||||
if (!info.ItemId.IsEmpty() && info.Item is null && libraryItem is not null)
|
||||
{
|
||||
var current = session.NowPlayingItem;
|
||||
|
||||
@ -1154,7 +1154,7 @@ namespace Emby.Server.Implementations.Session
|
||||
|
||||
var session = GetSessionToRemoteControl(sessionId);
|
||||
|
||||
var user = session.UserId.Equals(default) ? null : _userManager.GetUserById(session.UserId);
|
||||
var user = session.UserId.IsEmpty() ? null : _userManager.GetUserById(session.UserId);
|
||||
|
||||
List<BaseItem> items;
|
||||
|
||||
@ -1223,7 +1223,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var controllingSession = GetSession(controllingSessionId);
|
||||
AssertCanControl(session, controllingSession);
|
||||
if (!controllingSession.UserId.Equals(default))
|
||||
if (!controllingSession.UserId.IsEmpty())
|
||||
{
|
||||
command.ControllingUserId = controllingSession.UserId;
|
||||
}
|
||||
@ -1342,7 +1342,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
var controllingSession = GetSession(controllingSessionId);
|
||||
AssertCanControl(session, controllingSession);
|
||||
if (!controllingSession.UserId.Equals(default))
|
||||
if (!controllingSession.UserId.IsEmpty())
|
||||
{
|
||||
command.ControllingUserId = controllingSession.UserId.ToString("N", CultureInfo.InvariantCulture);
|
||||
}
|
||||
@ -1463,7 +1463,7 @@ namespace Emby.Server.Implementations.Session
|
||||
ArgumentException.ThrowIfNullOrEmpty(request.AppVersion);
|
||||
|
||||
User user = null;
|
||||
if (!request.UserId.Equals(default))
|
||||
if (!request.UserId.IsEmpty())
|
||||
{
|
||||
user = _userManager.GetUserById(request.UserId);
|
||||
}
|
||||
@ -1590,7 +1590,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
try
|
||||
{
|
||||
ReportSessionEnded(session.Id);
|
||||
await ReportSessionEnded(session.Id).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -1766,7 +1766,7 @@ namespace Emby.Server.Implementations.Session
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(info);
|
||||
|
||||
var user = info.UserId.Equals(default)
|
||||
var user = info.UserId.IsEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(info.UserId);
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.SyncPlay;
|
||||
@ -553,7 +554,7 @@ namespace Emby.Server.Implementations.SyncPlay
|
||||
if (playingItemRemoved)
|
||||
{
|
||||
var itemId = PlayQueue.GetPlayingItemId();
|
||||
if (!itemId.Equals(default))
|
||||
if (!itemId.IsEmpty())
|
||||
{
|
||||
var item = _libraryManager.GetItemById(itemId);
|
||||
RunTimeTicks = item.RunTimeTicks ?? 0;
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -41,7 +42,7 @@ namespace Emby.Server.Implementations.TV
|
||||
}
|
||||
|
||||
string? presentationUniqueKey = null;
|
||||
if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default))
|
||||
if (!query.SeriesId.IsNullOrEmpty())
|
||||
{
|
||||
if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
|
||||
{
|
||||
@ -91,7 +92,7 @@ namespace Emby.Server.Implementations.TV
|
||||
|
||||
string? presentationUniqueKey = null;
|
||||
int? limit = null;
|
||||
if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default))
|
||||
if (!request.SeriesId.IsNullOrEmpty())
|
||||
{
|
||||
if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
|
||||
{
|
||||
@ -146,7 +147,7 @@ namespace Emby.Server.Implementations.TV
|
||||
|
||||
// If viewing all next up for all series, remove first episodes
|
||||
// But if that returns empty, keep those first episodes (avoid completely empty view)
|
||||
var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default);
|
||||
var alwaysEnableFirstEpisode = !request.SeriesId.IsNullOrEmpty();
|
||||
var anyFound = false;
|
||||
|
||||
return allNextUp
|
||||
|
@ -11,6 +11,7 @@ using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Extensions;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
@ -227,7 +228,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
availablePackages = availablePackages.Where(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (!id.Equals(default))
|
||||
if (!id.IsEmpty())
|
||||
{
|
||||
availablePackages = availablePackages.Where(x => x.Id.Equals(id));
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -41,7 +42,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
||||
var isApiKey = context.User.GetIsApiKey();
|
||||
var userId = context.User.GetUserId();
|
||||
// This likely only happens during the wizard, so skip the default checks and let any other handlers do it
|
||||
if (!isApiKey && userId.Equals(default))
|
||||
if (!isApiKey && userId.IsEmpty())
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -46,7 +47,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
|
||||
}
|
||||
|
||||
var userId = contextUser.GetUserId();
|
||||
if (userId.Equals(default))
|
||||
if (userId.IsEmpty())
|
||||
{
|
||||
context.Fail();
|
||||
return Task.CompletedTask;
|
||||
|
@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -126,7 +127,7 @@ public class ArtistsController : BaseJellyfinApiController
|
||||
User? user = null;
|
||||
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
user = _userManager.GetUserById(userId.Value);
|
||||
}
|
||||
@ -330,7 +331,7 @@ public class ArtistsController : BaseJellyfinApiController
|
||||
User? user = null;
|
||||
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
user = _userManager.GetUserById(userId.Value);
|
||||
}
|
||||
@ -469,7 +470,7 @@ public class ArtistsController : BaseJellyfinApiController
|
||||
|
||||
var item = _libraryManager.GetArtist(name, dtoOptions);
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -126,7 +127,7 @@ public class ChannelsController : BaseJellyfinApiController
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -201,7 +202,7 @@ public class ChannelsController : BaseJellyfinApiController
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -3,6 +3,7 @@ using System.Linq;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -53,7 +54,7 @@ public class FilterController : BaseJellyfinApiController
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] MediaType[] mediaTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -146,7 +147,7 @@ public class FilterController : BaseJellyfinApiController
|
||||
[FromQuery] bool? recursive)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -95,7 +96,7 @@ public class GenresController : BaseJellyfinApiController
|
||||
.AddClientFields(User)
|
||||
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
||||
|
||||
User? user = userId.Value.Equals(default)
|
||||
User? user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -172,7 +173,7 @@ public class GenresController : BaseJellyfinApiController
|
||||
|
||||
item ??= new Genre();
|
||||
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -76,7 +77,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -113,7 +114,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var album = _libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -150,7 +151,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var playlist = (Playlist)_libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -186,7 +187,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -223,7 +224,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -260,7 +261,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
@ -334,7 +335,7 @@ public class InstantMixController : BaseJellyfinApiController
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
|
@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -245,7 +246,7 @@ public class ItemsController : BaseJellyfinApiController
|
||||
var isApiKey = User.GetIsApiKey();
|
||||
// if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = !isApiKey && !userId.Value.Equals(default)
|
||||
var user = !isApiKey && !userId.IsNullOrEmpty()
|
||||
? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
|
||||
: null;
|
||||
|
||||
@ -840,7 +841,7 @@ public class ItemsController : BaseJellyfinApiController
|
||||
var ancestorIds = Array.Empty<Guid>();
|
||||
|
||||
var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
|
||||
if (parentIdGuid.Equals(default) && excludeFolderIds.Length > 0)
|
||||
if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0)
|
||||
{
|
||||
ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.Where(i => i is Folder)
|
||||
|
@ -146,12 +146,12 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromQuery] bool inheritFromParent = false)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
? (userId.Value.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? (userId.IsNullOrEmpty()
|
||||
? _libraryManager.RootFolder
|
||||
: _libraryManager.GetUserRootFolder())
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
@ -213,12 +213,12 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromQuery] bool inheritFromParent = false)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
? (userId.Value.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? (userId.IsNullOrEmpty()
|
||||
? _libraryManager.RootFolder
|
||||
: _libraryManager.GetUserRootFolder())
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
@ -339,7 +339,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
{
|
||||
var isApiKey = User.GetIsApiKey();
|
||||
var userId = User.GetUserId();
|
||||
var user = !isApiKey && !userId.Equals(default)
|
||||
var user = !isApiKey && !userId.IsEmpty()
|
||||
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
|
||||
: null;
|
||||
if (!isApiKey && user is null)
|
||||
@ -382,7 +382,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
{
|
||||
var isApiKey = User.GetIsApiKey();
|
||||
var userId = User.GetUserId();
|
||||
var user = !isApiKey && !userId.Equals(default)
|
||||
var user = !isApiKey && !userId.IsEmpty()
|
||||
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
|
||||
: null;
|
||||
|
||||
@ -428,7 +428,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromQuery] bool? isFavorite)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -471,7 +471,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
|
||||
var baseItemDtos = new List<BaseItemDto>();
|
||||
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -702,8 +702,8 @@ public class LibraryController : BaseJellyfinApiController
|
||||
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var item = itemId.Equals(default)
|
||||
? (userId.Value.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? (userId.IsNullOrEmpty()
|
||||
? _libraryManager.RootFolder
|
||||
: _libraryManager.GetUserRootFolder())
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
@ -718,7 +718,7 @@ public class LibraryController : BaseJellyfinApiController
|
||||
return new QueryResult<BaseItemDto>();
|
||||
}
|
||||
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
|
@ -10,12 +10,12 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Attributes;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.LiveTvDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
@ -43,6 +43,8 @@ namespace Jellyfin.Api.Controllers;
|
||||
public class LiveTvController : BaseJellyfinApiController
|
||||
{
|
||||
private readonly ILiveTvManager _liveTvManager;
|
||||
private readonly IGuideManager _guideManager;
|
||||
private readonly ITunerHostManager _tunerHostManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
@ -55,6 +57,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// Initializes a new instance of the <see cref="LiveTvController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="liveTvManager">Instance of the <see cref="ILiveTvManager"/> interface.</param>
|
||||
/// <param name="guideManager">Instance of the <see cref="IGuideManager"/> interface.</param>
|
||||
/// <param name="tunerHostManager">Instance of the <see cref="ITunerHostManager"/> interface.</param>
|
||||
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
|
||||
/// <param name="httpClientFactory">Instance of the <see cref="IHttpClientFactory"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
@ -64,6 +68,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
/// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
|
||||
public LiveTvController(
|
||||
ILiveTvManager liveTvManager,
|
||||
IGuideManager guideManager,
|
||||
ITunerHostManager tunerHostManager,
|
||||
IUserManager userManager,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILibraryManager libraryManager,
|
||||
@ -73,6 +79,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
ITranscodeManager transcodeManager)
|
||||
{
|
||||
_liveTvManager = liveTvManager;
|
||||
_guideManager = guideManager;
|
||||
_tunerHostManager = tunerHostManager;
|
||||
_userManager = userManager;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_libraryManager = libraryManager;
|
||||
@ -179,7 +187,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
dtoOptions,
|
||||
CancellationToken.None);
|
||||
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -211,10 +219,10 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var item = channelId.Equals(default)
|
||||
var item = channelId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(channelId);
|
||||
|
||||
@ -384,7 +392,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecordingFolders([FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var folders = await _liveTvManager.GetRecordingFoldersAsync(user).ConfigureAwait(false);
|
||||
@ -407,10 +415,10 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
|
||||
var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
|
||||
|
||||
var dtoOptions = new DtoOptions()
|
||||
.AddClientFields(User);
|
||||
@ -564,7 +572,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -591,7 +599,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
GenreIds = genreIds
|
||||
};
|
||||
|
||||
if (librarySeriesId.HasValue && !librarySeriesId.Equals(default))
|
||||
if (!librarySeriesId.IsNullOrEmpty())
|
||||
{
|
||||
query.IsSeries = true;
|
||||
|
||||
@ -620,7 +628,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
|
||||
{
|
||||
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
|
||||
var user = body.UserId.IsEmpty() ? null : _userManager.GetUserById(body.UserId);
|
||||
|
||||
var query = new InternalItemsQuery(user)
|
||||
{
|
||||
@ -645,7 +653,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
GenreIds = body.GenreIds
|
||||
};
|
||||
|
||||
if (!body.LibrarySeriesId.Equals(default))
|
||||
if (!body.LibrarySeriesId.IsEmpty())
|
||||
{
|
||||
query.IsSeries = true;
|
||||
|
||||
@ -704,7 +712,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] bool enableTotalRecordCount = true)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -743,7 +751,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -937,9 +945,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<GuideInfo> GetGuideInfo()
|
||||
{
|
||||
return _liveTvManager.GetGuideInfo();
|
||||
}
|
||||
=> _guideManager.GetGuideInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a tuner host.
|
||||
@ -951,9 +957,7 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
|
||||
{
|
||||
return await _liveTvManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
}
|
||||
=> await _tunerHostManager.SaveTunerHost(tunerHostInfo).ConfigureAwait(false);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a tuner host.
|
||||
@ -1130,10 +1134,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[HttpGet("TunerHosts/Types")]
|
||||
[Authorize(Policy = Policies.LiveTvAccess)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
|
||||
{
|
||||
return _liveTvManager.GetTunerHostTypes();
|
||||
}
|
||||
public IEnumerable<NameIdPair> GetTunerHostTypes()
|
||||
=> _tunerHostManager.GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Discover tuners.
|
||||
@ -1145,10 +1147,8 @@ public class LiveTvController : BaseJellyfinApiController
|
||||
[HttpGet("Tuners/Discover")]
|
||||
[Authorize(Policy = Policies.LiveTvManagement)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||
{
|
||||
return await _liveTvManager.DiscoverTuners(newDevicesOnly, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
public IAsyncEnumerable<TunerHostInfo> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
|
||||
=> _tunerHostManager.DiscoverTuners(newDevicesOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a live tv recording stream.
|
||||
|
@ -7,6 +7,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
@ -69,7 +70,7 @@ public class MoviesController : BaseJellyfinApiController
|
||||
[FromQuery] int itemLimit = 8)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var dtoOptions = new DtoOptions { Fields = fields }
|
||||
|
@ -6,6 +6,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@ -95,7 +96,7 @@ public class MusicGenresController : BaseJellyfinApiController
|
||||
.AddClientFields(User)
|
||||
.AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
|
||||
|
||||
User? user = userId.Value.Equals(default)
|
||||
User? user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -164,7 +165,7 @@ public class MusicGenresController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -83,7 +84,7 @@ public class PersonsController : BaseJellyfinApiController
|
||||
.AddClientFields(User)
|
||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||
|
||||
User? user = userId.Value.Equals(default)
|
||||
User? user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -129,7 +130,7 @@ public class PersonsController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||
|
@ -9,6 +9,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.PlaylistDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Playlists;
|
||||
@ -188,7 +189,7 @@ public class PlaylistsController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var user = userId.Equals(default)
|
||||
var user = userId.IsEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId);
|
||||
|
||||
|
@ -209,7 +209,7 @@ public class SearchController : BaseJellyfinApiController
|
||||
break;
|
||||
}
|
||||
|
||||
if (!item.ChannelId.Equals(default))
|
||||
if (!item.ChannelId.IsEmpty())
|
||||
{
|
||||
var channel = _libraryManager.GetItemById(item.ChannelId);
|
||||
result.ChannelName = channel?.Name;
|
||||
|
@ -10,6 +10,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Api.Models.SessionDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -71,7 +72,7 @@ public class SessionController : BaseJellyfinApiController
|
||||
result = result.Where(i => string.Equals(i.DeviceId, deviceId, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
if (controllableByUserId.HasValue && !controllableByUserId.Equals(default))
|
||||
if (!controllableByUserId.IsNullOrEmpty())
|
||||
{
|
||||
result = result.Where(i => i.SupportsRemoteControl);
|
||||
|
||||
@ -83,12 +84,12 @@ public class SessionController : BaseJellyfinApiController
|
||||
|
||||
if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
|
||||
{
|
||||
result = result.Where(i => i.UserId.Equals(default) || i.ContainsUser(controllableByUserId.Value));
|
||||
result = result.Where(i => i.UserId.IsEmpty() || i.ContainsUser(controllableByUserId.Value));
|
||||
}
|
||||
|
||||
if (!user.HasPermission(PermissionKind.EnableSharedDeviceControl))
|
||||
{
|
||||
result = result.Where(i => !i.UserId.Equals(default));
|
||||
result = result.Where(i => !i.UserId.IsEmpty());
|
||||
}
|
||||
|
||||
result = result.Where(i =>
|
||||
|
@ -5,6 +5,7 @@ using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -91,7 +92,7 @@ public class StudiosController : BaseJellyfinApiController
|
||||
.AddClientFields(User)
|
||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||
|
||||
User? user = userId.Value.Equals(default)
|
||||
User? user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -144,7 +145,7 @@ public class StudiosController : BaseJellyfinApiController
|
||||
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||
|
||||
var item = _libraryManager.GetStudio(name);
|
||||
if (!userId.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
@ -62,7 +63,7 @@ public class SuggestionsController : BaseJellyfinApiController
|
||||
[FromQuery] int? limit,
|
||||
[FromQuery] bool enableTotalRecordCount = false)
|
||||
{
|
||||
var user = userId.Equals(default)
|
||||
var user = userId.IsEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId);
|
||||
|
||||
|
@ -111,7 +111,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
},
|
||||
options);
|
||||
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -150,7 +150,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -222,7 +222,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] ItemSortBy? sortBy)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
@ -284,7 +284,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
}
|
||||
|
||||
// This must be the last filter
|
||||
if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default))
|
||||
if (!adjacentTo.IsNullOrEmpty())
|
||||
{
|
||||
episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
|
||||
}
|
||||
@ -339,7 +339,7 @@ public class TvShowsController : BaseJellyfinApiController
|
||||
[FromQuery] bool? enableUserData)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
|
@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.Models.UserDtos;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
@ -532,7 +533,7 @@ public class UserController : BaseJellyfinApiController
|
||||
public ActionResult<UserDto> GetCurrentUser()
|
||||
{
|
||||
var userId = User.GetUserId();
|
||||
if (userId.Equals(default))
|
||||
if (userId.IsEmpty())
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
@ -84,7 +85,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -145,7 +146,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -185,7 +186,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -221,7 +222,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -257,7 +258,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -294,7 +295,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -330,7 +331,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -375,7 +376,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
@ -558,7 +559,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
||||
|
@ -11,6 +11,7 @@ using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Api.Helpers;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Net;
|
||||
@ -96,12 +97,12 @@ public class VideosController : BaseJellyfinApiController
|
||||
public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
|
||||
{
|
||||
userId = RequestHelpers.GetUserId(User, userId);
|
||||
var user = userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
|
||||
var item = itemId.Equals(default)
|
||||
? (userId.Value.Equals(default)
|
||||
var item = itemId.IsEmpty()
|
||||
? (userId.IsNullOrEmpty()
|
||||
? _libraryManager.RootFolder
|
||||
: _libraryManager.GetUserRootFolder())
|
||||
: _libraryManager.GetItemById(itemId);
|
||||
|
@ -90,7 +90,7 @@ public class YearsController : BaseJellyfinApiController
|
||||
.AddClientFields(User)
|
||||
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
|
||||
|
||||
User? user = userId.Value.Equals(default)
|
||||
User? user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
|
||||
@ -110,7 +110,7 @@ public class YearsController : BaseJellyfinApiController
|
||||
{
|
||||
var folder = (Folder)parentItem;
|
||||
|
||||
if (userId.Equals(default))
|
||||
if (userId.IsNullOrEmpty())
|
||||
{
|
||||
items = recursive ? folder.GetRecursiveChildren(Filter) : folder.Children.Where(Filter).ToList();
|
||||
}
|
||||
@ -182,7 +182,7 @@ public class YearsController : BaseJellyfinApiController
|
||||
var dtoOptions = new DtoOptions()
|
||||
.AddClientFields(User);
|
||||
|
||||
if (!userId.Value.Equals(default))
|
||||
if (!userId.IsNullOrEmpty())
|
||||
{
|
||||
var user = _userManager.GetUserById(userId.Value);
|
||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||
|
@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
@ -86,7 +87,7 @@ public class MediaInfoHelper
|
||||
string? mediaSourceId = null,
|
||||
string? liveStreamId = null)
|
||||
{
|
||||
var user = userId is null || userId.Value.Equals(default)
|
||||
var user = userId.IsNullOrEmpty()
|
||||
? null
|
||||
: _userManager.GetUserById(userId.Value);
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
@ -7,6 +7,7 @@ using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Extensions;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
@ -67,7 +68,7 @@ public static class RequestHelpers
|
||||
var authenticatedUserId = claimsPrincipal.GetUserId();
|
||||
|
||||
// UserId not provided, fall back to authenticated user id.
|
||||
if (userId is null || userId.Value.Equals(default))
|
||||
if (userId.IsNullOrEmpty())
|
||||
{
|
||||
return authenticatedUserId;
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public static class StreamingHelpers
|
||||
};
|
||||
|
||||
var userId = httpContext.User.GetUserId();
|
||||
if (!userId.Equals(default))
|
||||
if (!userId.IsEmpty())
|
||||
{
|
||||
state.User = userManager.GetUserById(userId);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Data.Events;
|
||||
using Jellyfin.Data.Events.Users;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
@ -117,7 +118,7 @@ namespace Jellyfin.Server.Implementations.Users
|
||||
/// <inheritdoc/>
|
||||
public User? GetUserById(Guid id)
|
||||
{
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(id));
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ using Jellyfin.Api.WebSocketListeners;
|
||||
using Jellyfin.Drawing;
|
||||
using Jellyfin.Drawing.Skia;
|
||||
using Jellyfin.LiveTv;
|
||||
using Jellyfin.LiveTv.Channels;
|
||||
using Jellyfin.Server.Implementations;
|
||||
using Jellyfin.Server.Implementations.Activity;
|
||||
using Jellyfin.Server.Implementations.Devices;
|
||||
@ -18,18 +17,15 @@ using Jellyfin.Server.Implementations.Users;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.BaseItemManager;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
using MediaBrowser.Controller.Events;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Lyrics;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Controller.Security;
|
||||
using MediaBrowser.Controller.Trickplay;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Providers.Lyric;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -101,11 +97,6 @@ namespace Jellyfin.Server
|
||||
|
||||
serviceCollection.AddScoped<IAuthenticationManager, AuthenticationManager>();
|
||||
|
||||
serviceCollection.AddSingleton<LiveTvDtoService>();
|
||||
serviceCollection.AddSingleton<ILiveTvManager, LiveTvManager>();
|
||||
serviceCollection.AddSingleton<IChannelManager, ChannelManager>();
|
||||
serviceCollection.AddSingleton<IStreamHelper, StreamHelper>();
|
||||
|
||||
foreach (var type in GetExportTypes<ILyricProvider>())
|
||||
{
|
||||
serviceCollection.AddSingleton(typeof(ILyricProvider), type);
|
||||
|
@ -5,6 +5,7 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using Jellyfin.LiveTv.Extensions;
|
||||
using Jellyfin.MediaEncoding.Hls.Extensions;
|
||||
using Jellyfin.Networking;
|
||||
using Jellyfin.Networking.HappyEyeballs;
|
||||
@ -121,6 +122,7 @@ namespace Jellyfin.Server
|
||||
.AddCheck<DbContextFactoryHealthCheck<JellyfinDbContext>>(nameof(JellyfinDbContext));
|
||||
|
||||
services.AddHlsPlaylistGenerator();
|
||||
services.AddLiveTvServices();
|
||||
|
||||
services.AddHostedService<AutoDiscoveryHost>();
|
||||
}
|
||||
|
@ -95,12 +95,5 @@ namespace MediaBrowser.Controller.Channels
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The item media sources.</returns>
|
||||
IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the item supports media probe.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>Whether media probe should be enabled.</returns>
|
||||
bool EnableMediaProbe(BaseItem item);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@ -184,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <exception cref="ArgumentNullException">The id is empty.</exception>
|
||||
public BaseItem FindVirtualChild(Guid id)
|
||||
{
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
throw new ArgumentNullException(nameof(id));
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities.Audio
|
||||
public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasLookupInfo<ArtistInfo>
|
||||
{
|
||||
[JsonIgnore]
|
||||
public bool IsAccessedByName => ParentId.Equals(default);
|
||||
public bool IsAccessedByName => ParentId.IsEmpty();
|
||||
|
||||
[JsonIgnore]
|
||||
public override bool IsFolder => !IsAccessedByName;
|
||||
|
@ -240,7 +240,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!ChannelId.Equals(default))
|
||||
if (!ChannelId.IsEmpty())
|
||||
{
|
||||
return SourceType.Channel;
|
||||
}
|
||||
@ -530,7 +530,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
get
|
||||
{
|
||||
var id = DisplayParentId;
|
||||
if (id.Equals(default))
|
||||
if (id.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -746,7 +746,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public virtual bool StopRefreshIfLocalMetadataFound => true;
|
||||
|
||||
[JsonIgnore]
|
||||
protected virtual bool SupportsOwnedItems => !ParentId.Equals(default) && IsFileProtocol;
|
||||
protected virtual bool SupportsOwnedItems => !ParentId.IsEmpty() && IsFileProtocol;
|
||||
|
||||
[JsonIgnore]
|
||||
public virtual bool SupportsPeople => false;
|
||||
@ -823,7 +823,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public BaseItem GetOwner()
|
||||
{
|
||||
var ownerId = OwnerId;
|
||||
return ownerId.Equals(default) ? null : LibraryManager.GetItemById(ownerId);
|
||||
return ownerId.IsEmpty() ? null : LibraryManager.GetItemById(ownerId);
|
||||
}
|
||||
|
||||
public bool CanDelete(User user, List<Folder> allCollectionFolders)
|
||||
@ -968,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
public BaseItem GetParent()
|
||||
{
|
||||
var parentId = ParentId;
|
||||
if (parentId.Equals(default))
|
||||
if (parentId.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -1361,7 +1361,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
var tasks = extras.Select(i =>
|
||||
{
|
||||
var subOptions = new MetadataRefreshOptions(options);
|
||||
if (!i.OwnerId.Equals(ownerId) || !i.ParentId.Equals(default))
|
||||
if (!i.OwnerId.Equals(ownerId) || !i.ParentId.IsEmpty())
|
||||
{
|
||||
i.OwnerId = ownerId;
|
||||
i.ParentId = Guid.Empty;
|
||||
@ -1673,7 +1673,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
// First get using the cached Id
|
||||
if (info.ItemId.HasValue)
|
||||
{
|
||||
if (info.ItemId.Value.Equals(default))
|
||||
if (info.ItemId.Value.IsEmpty())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -2439,7 +2439,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
if (video.OwnerId.Equals(default))
|
||||
if (video.OwnerId.IsEmpty())
|
||||
{
|
||||
video.OwnerId = this.Id;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Collections;
|
||||
@ -198,7 +199,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
item.SetParent(this);
|
||||
|
||||
if (item.Id.Equals(default))
|
||||
if (item.Id.IsEmpty())
|
||||
{
|
||||
item.Id = LibraryManager.GetNewItemId(item.Path, item.GetType());
|
||||
}
|
||||
@ -697,7 +698,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (this is not UserRootFolder
|
||||
&& this is not AggregateFolder
|
||||
&& query.ParentId.Equals(default))
|
||||
&& query.ParentId.IsEmpty())
|
||||
{
|
||||
query.Parent = this;
|
||||
}
|
||||
@ -840,7 +841,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return true;
|
||||
}
|
||||
|
||||
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
|
||||
if (!query.AdjacentTo.IsNullOrEmpty())
|
||||
{
|
||||
Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
|
||||
return true;
|
||||
@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
#pragma warning restore CA1309
|
||||
|
||||
// This must be the last filter
|
||||
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
|
||||
if (!query.AdjacentTo.IsNullOrEmpty())
|
||||
{
|
||||
items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -74,12 +75,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
get
|
||||
{
|
||||
var seriesId = SeriesId;
|
||||
if (seriesId.Equals(default))
|
||||
if (seriesId.IsEmpty())
|
||||
{
|
||||
seriesId = FindSeriesId();
|
||||
}
|
||||
|
||||
return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series);
|
||||
return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,12 +90,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
get
|
||||
{
|
||||
var seasonId = SeasonId;
|
||||
if (seasonId.Equals(default))
|
||||
if (seasonId.IsEmpty())
|
||||
{
|
||||
seasonId = FindSeasonId();
|
||||
}
|
||||
|
||||
return seasonId.Equals(default) ? null : (LibraryManager.GetItemById(seasonId) as Season);
|
||||
return seasonId.IsEmpty() ? null : (LibraryManager.GetItemById(seasonId) as Season);
|
||||
}
|
||||
}
|
||||
|
||||
@ -271,7 +272,7 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
|
||||
var seasonId = SeasonId;
|
||||
|
||||
if (!seasonId.Equals(default) && !list.Contains(seasonId))
|
||||
if (!seasonId.IsEmpty() && !list.Contains(seasonId))
|
||||
{
|
||||
list.Add(seasonId);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Jellyfin.Data.Entities;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Controller.Dto;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Querying;
|
||||
@ -48,12 +49,12 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
get
|
||||
{
|
||||
var seriesId = SeriesId;
|
||||
if (seriesId.Equals(default))
|
||||
if (seriesId.IsEmpty())
|
||||
{
|
||||
seriesId = FindSeriesId();
|
||||
}
|
||||
|
||||
return seriesId.Equals(default) ? null : (LibraryManager.GetItemById(seriesId) as Series);
|
||||
return seriesId.IsEmpty() ? null : (LibraryManager.GetItemById(seriesId) as Series);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,11 +70,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Guid> GetIdsForAncestorQuery()
|
||||
{
|
||||
if (!DisplayParentId.Equals(default))
|
||||
if (!DisplayParentId.IsEmpty())
|
||||
{
|
||||
yield return DisplayParentId;
|
||||
}
|
||||
else if (!ParentId.Equals(default))
|
||||
else if (!ParentId.IsEmpty())
|
||||
{
|
||||
yield return ParentId;
|
||||
}
|
||||
@ -95,11 +95,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
var parent = this as Folder;
|
||||
|
||||
if (!DisplayParentId.Equals(default))
|
||||
if (!DisplayParentId.IsEmpty())
|
||||
{
|
||||
parent = LibraryManager.GetItemById(DisplayParentId) as Folder ?? parent;
|
||||
}
|
||||
else if (!ParentId.Equals(default))
|
||||
else if (!ParentId.IsEmpty())
|
||||
{
|
||||
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
|
||||
}
|
||||
|
@ -433,7 +433,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
var user = query.User;
|
||||
|
||||
// This must be the last filter
|
||||
if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
|
||||
if (!query.AdjacentTo.IsNullOrEmpty())
|
||||
{
|
||||
items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
|
||||
}
|
||||
|
@ -456,7 +456,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
foreach (var child in LinkedAlternateVersions)
|
||||
{
|
||||
// Reset the cached value
|
||||
if (child.ItemId.HasValue && child.ItemId.Value.Equals(default))
|
||||
if (child.ItemId.IsNullOrEmpty())
|
||||
{
|
||||
child.ItemId = null;
|
||||
}
|
||||
|
26
MediaBrowser.Controller/LiveTv/IGuideManager.cs
Normal file
26
MediaBrowser.Controller/LiveTv/IGuideManager.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for managing the Live TV guide.
|
||||
/// </summary>
|
||||
public interface IGuideManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the guide information.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="GuideInfo"/>.</returns>
|
||||
GuideInfo GetGuideInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Refresh the guide.
|
||||
/// </summary>
|
||||
/// <param name="progress">The <see cref="IProgress{T}"/> to use.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>Task representing the refresh operation.</returns>
|
||||
Task RefreshGuide(IProgress<double> progress, CancellationToken cancellationToken);
|
||||
}
|
@ -71,9 +71,8 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// Adds the parts.
|
||||
/// </summary>
|
||||
/// <param name="services">The services.</param>
|
||||
/// <param name="tunerHosts">The tuner hosts.</param>
|
||||
/// <param name="listingProviders">The listing providers.</param>
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<ITunerHost> tunerHosts, IEnumerable<IListingsProvider> listingProviders);
|
||||
void AddParts(IEnumerable<ILiveTvService> services, IEnumerable<IListingsProvider> listingProviders);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the timer.
|
||||
@ -175,12 +174,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the guide information.
|
||||
/// </summary>
|
||||
/// <returns>GuideInfo.</returns>
|
||||
GuideInfo GetGuideInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the recommended programs.
|
||||
/// </summary>
|
||||
@ -253,14 +246,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the listing provider.
|
||||
/// </summary>
|
||||
@ -298,10 +283,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
|
||||
Task<List<ChannelInfo>> GetChannelsFromListingsProviderData(string id, CancellationToken cancellationToken);
|
||||
|
||||
List<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
Task<List<TunerHostInfo>> DiscoverTuners(bool newDevicesOnly, CancellationToken cancellationToken);
|
||||
|
||||
string GetEmbyTvActiveRecordingPath(string id);
|
||||
|
||||
ActiveRecordingInfo GetActiveRecordingInfo(string path);
|
||||
|
@ -140,14 +140,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task.</returns>
|
||||
Task CloseLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Records the live stream.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
Task RecordLiveStream(string id, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the tuner.
|
||||
/// </summary>
|
||||
@ -180,9 +172,4 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
Task<ILiveStream> GetChannelStreamWithDirectStreamProvider(string channelId, string streamId, List<ILiveStream> currentLiveStreams, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public interface ISupportsUpdatingDefaults
|
||||
{
|
||||
Task UpdateTimerDefaults(SeriesTimerInfo info, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// <returns>Task<IEnumerable<ChannelInfo>>.</returns>
|
||||
Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tuner infos.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task<List<LiveTvTunerInfo>>.</returns>
|
||||
Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the channel stream.
|
||||
/// </summary>
|
||||
|
46
MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
Normal file
46
MediaBrowser.Controller/LiveTv/ITunerHostManager.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv;
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for managing the <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
public interface ITunerHostManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
IReadOnlyList<ITunerHost> TunerHosts { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="NameIdPair"/>s for the available <see cref="ITunerHost"/>s.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="NameIdPair"/>s.</returns>
|
||||
IEnumerable<NameIdPair> GetTunerHostTypes();
|
||||
|
||||
/// <summary>
|
||||
/// Saves the tuner host.
|
||||
/// </summary>
|
||||
/// <param name="info">Turner host to save.</param>
|
||||
/// <param name="dataSourceChanged">Option to specify that data source has changed.</param>
|
||||
/// <returns>Tuner host information wrapped in a task.</returns>
|
||||
Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true);
|
||||
|
||||
/// <summary>
|
||||
/// Discovers the available tuners.
|
||||
/// </summary>
|
||||
/// <param name="newDevicesOnly">A value indicating whether to only return new devices.</param>
|
||||
/// <returns>The <see cref="TunerHostInfo"/>s.</returns>
|
||||
IAsyncEnumerable<TunerHostInfo> DiscoverTuners(bool newDevicesOnly);
|
||||
|
||||
/// <summary>
|
||||
/// Scans for tuner devices that have changed URLs.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to use.</param>
|
||||
/// <returns>A task that represents the scanning operation.</returns>
|
||||
Task ScanForTunerDeviceChanges(CancellationToken cancellationToken);
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class LiveTvServiceStatusInfo
|
||||
{
|
||||
public LiveTvServiceStatusInfo()
|
||||
{
|
||||
Tuners = new List<LiveTvTunerInfo>();
|
||||
IsVisible = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public LiveTvServiceStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status message.
|
||||
/// </summary>
|
||||
/// <value>The status message.</value>
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version.
|
||||
/// </summary>
|
||||
/// <value>The version.</value>
|
||||
public string Version { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has update available.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance has update available; otherwise, <c>false</c>.</value>
|
||||
public bool HasUpdateAvailable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tuners.
|
||||
/// </summary>
|
||||
/// <value>The tuners.</value>
|
||||
public List<LiveTvTunerInfo> Tuners { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is visible.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is visible; otherwise, <c>false</c>.</value>
|
||||
public bool IsVisible { get; set; }
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class LiveTvTunerInfo
|
||||
{
|
||||
public LiveTvTunerInfo()
|
||||
{
|
||||
Clients = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the source.
|
||||
/// </summary>
|
||||
/// <value>The type of the source.</value>
|
||||
public string SourceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier.
|
||||
/// </summary>
|
||||
/// <value>The identifier.</value>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
/// <value>The URL.</value>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public LiveTvTunerStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel identifier.
|
||||
/// </summary>
|
||||
/// <value>The channel identifier.</value>
|
||||
public string ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the recording identifier.
|
||||
/// </summary>
|
||||
/// <value>The recording identifier.</value>
|
||||
public string RecordingId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the program.
|
||||
/// </summary>
|
||||
/// <value>The name of the program.</value>
|
||||
public string ProgramName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the clients.
|
||||
/// </summary>
|
||||
/// <value>The clients.</value>
|
||||
public List<string> Clients { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance can reset.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance can reset; otherwise, <c>false</c>.</value>
|
||||
public bool CanReset { get; set; }
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class RecordingInfo
|
||||
{
|
||||
public RecordingInfo()
|
||||
{
|
||||
Genres = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the id of the recording.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the series timer identifier.
|
||||
/// </summary>
|
||||
/// <value>The series timer identifier.</value>
|
||||
public string SeriesTimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timer identifier.
|
||||
/// </summary>
|
||||
/// <value>The timer identifier.</value>
|
||||
public string TimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channelId of the recording.
|
||||
/// </summary>
|
||||
public string ChannelId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the channel.
|
||||
/// </summary>
|
||||
/// <value>The type of the channel.</value>
|
||||
public ChannelType ChannelType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the recording.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the path.
|
||||
/// </summary>
|
||||
/// <value>The path.</value>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL.
|
||||
/// </summary>
|
||||
/// <value>The URL.</value>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the overview.
|
||||
/// </summary>
|
||||
/// <value>The overview.</value>
|
||||
public string Overview { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the start date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime StartDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the end date of the recording, in UTC.
|
||||
/// </summary>
|
||||
public DateTime EndDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the program identifier.
|
||||
/// </summary>
|
||||
/// <value>The program identifier.</value>
|
||||
public string ProgramId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the status.
|
||||
/// </summary>
|
||||
/// <value>The status.</value>
|
||||
public RecordingStatus Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the genre of the program.
|
||||
/// </summary>
|
||||
public List<string> Genres { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is repeat.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value>
|
||||
public bool IsRepeat { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the episode title.
|
||||
/// </summary>
|
||||
/// <value>The episode title.</value>
|
||||
public string EpisodeTitle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is hd.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
|
||||
public bool? IsHD { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the audio.
|
||||
/// </summary>
|
||||
/// <value>The audio.</value>
|
||||
public ProgramAudio? Audio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the original air date.
|
||||
/// </summary>
|
||||
/// <value>The original air date.</value>
|
||||
public DateTime? OriginalAirDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is movie.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is movie; otherwise, <c>false</c>.</value>
|
||||
public bool IsMovie { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is sports.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
|
||||
public bool IsSports { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is series.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is series; otherwise, <c>false</c>.</value>
|
||||
public bool IsSeries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is live.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
|
||||
public bool IsLive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is news.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
|
||||
public bool IsNews { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is kids.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
|
||||
public bool IsKids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is premiere.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
|
||||
public bool IsPremiere { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the official rating.
|
||||
/// </summary>
|
||||
/// <value>The official rating.</value>
|
||||
public string OfficialRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the community rating.
|
||||
/// </summary>
|
||||
/// <value>The community rating.</value>
|
||||
public float? CommunityRating { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image path if it can be accessed directly from the file system.
|
||||
/// </summary>
|
||||
/// <value>The image path.</value>
|
||||
public string ImagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the image url if it can be downloaded.
|
||||
/// </summary>
|
||||
/// <value>The image URL.</value>
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance has image.
|
||||
/// </summary>
|
||||
/// <value><c>null</c> if [has image] contains no value, <c>true</c> if [has image]; otherwise, <c>false</c>.</value>
|
||||
public bool? HasImage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the show identifier.
|
||||
/// </summary>
|
||||
/// <value>The show identifier.</value>
|
||||
public string ShowId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date last updated.
|
||||
/// </summary>
|
||||
/// <value>The date last updated.</value>
|
||||
public DateTime DateLastUpdated { get; set; }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
#nullable disable
|
||||
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
|
||||
namespace MediaBrowser.Controller.LiveTv
|
||||
{
|
||||
public class RecordingStatusChangedEventArgs : EventArgs
|
||||
{
|
||||
public string RecordingId { get; set; }
|
||||
|
||||
public RecordingStatus NewStatus { get; set; }
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
private const string VaapiAlias = "va";
|
||||
private const string D3d11vaAlias = "dx11";
|
||||
private const string VideotoolboxAlias = "vt";
|
||||
private const string RkmppAlias = "rk";
|
||||
private const string OpenclAlias = "ocl";
|
||||
private const string CudaAlias = "cu";
|
||||
private const string DrmAlias = "dr";
|
||||
@ -161,6 +162,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{ "vaapi", hwEncoder + "_vaapi" },
|
||||
{ "videotoolbox", hwEncoder + "_videotoolbox" },
|
||||
{ "v4l2m2m", hwEncoder + "_v4l2m2m" },
|
||||
{ "rkmpp", hwEncoder + "_rkmpp" },
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(hwType)
|
||||
@ -217,6 +219,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
&& _mediaEncoder.SupportsFilter("hwupload_vaapi");
|
||||
}
|
||||
|
||||
private bool IsRkmppFullSupported()
|
||||
{
|
||||
return _mediaEncoder.SupportsHwaccel("rkmpp")
|
||||
&& _mediaEncoder.SupportsFilter("scale_rkrga")
|
||||
&& _mediaEncoder.SupportsFilter("vpp_rkrga")
|
||||
&& _mediaEncoder.SupportsFilter("overlay_rkrga");
|
||||
}
|
||||
|
||||
private bool IsOpenclFullSupported()
|
||||
{
|
||||
return _mediaEncoder.SupportsHwaccel("opencl")
|
||||
@ -696,6 +706,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return codec.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private string GetRkmppDeviceArgs(string alias)
|
||||
{
|
||||
alias ??= RkmppAlias;
|
||||
|
||||
// device selection in rk is not supported.
|
||||
return " -init_hw_device rkmpp=" + alias;
|
||||
}
|
||||
|
||||
private string GetVideoToolboxDeviceArgs(string alias)
|
||||
{
|
||||
alias ??= VideotoolboxAlias;
|
||||
@ -835,30 +853,25 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
|
||||
{
|
||||
// DVBSUB and DVDSUB use the fixed canvas size 720x576
|
||||
// DVBSUB uses the fixed canvas size 720x576
|
||||
if (state.SubtitleStream is not null
|
||||
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
|
||||
&& !state.SubtitleStream.IsTextSubtitleStream
|
||||
&& !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase))
|
||||
&& !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var inW = state.VideoStream?.Width;
|
||||
var inH = state.VideoStream?.Height;
|
||||
var reqW = state.BaseRequest.Width;
|
||||
var reqH = state.BaseRequest.Height;
|
||||
var reqMaxW = state.BaseRequest.MaxWidth;
|
||||
var reqMaxH = state.BaseRequest.MaxHeight;
|
||||
var subtitleWidth = state.SubtitleStream?.Width;
|
||||
var subtitleHeight = state.SubtitleStream?.Height;
|
||||
|
||||
// setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead
|
||||
var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080);
|
||||
|
||||
if (overlayW.HasValue && overlayH.HasValue)
|
||||
if (subtitleWidth.HasValue
|
||||
&& subtitleHeight.HasValue
|
||||
&& subtitleWidth.Value > 0
|
||||
&& subtitleHeight.Value > 0)
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -canvas_size {0}x{1}",
|
||||
overlayW.Value,
|
||||
overlayH.Value);
|
||||
subtitleWidth.Value,
|
||||
subtitleHeight.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1061,6 +1074,33 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
// no videotoolbox hw filter.
|
||||
args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
|
||||
}
|
||||
else if (string.Equals(optHwaccelType, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!isLinux || !_mediaEncoder.SupportsHwaccel("rkmpp"))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
|
||||
var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
|
||||
if (!isRkmppDecoder && !isRkmppEncoder)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
args.Append(GetRkmppDeviceArgs(RkmppAlias));
|
||||
|
||||
var filterDevArgs = string.Empty;
|
||||
var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
|
||||
|
||||
if (doOclTonemap && !isRkmppDecoder)
|
||||
{
|
||||
args.Append(GetOpenclDeviceArgs(0, null, RkmppAlias, OpenclAlias));
|
||||
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
|
||||
}
|
||||
|
||||
args.Append(filterDevArgs);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vidDecoder))
|
||||
{
|
||||
@ -1477,8 +1517,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
if (string.Equals(codec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "av1_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "av1_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "av1_amf", StringComparison.OrdinalIgnoreCase)
|
||||
@ -1918,20 +1960,22 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
profile = "constrained_baseline";
|
||||
}
|
||||
|
||||
// libx264, h264_qsv and h264_nvenc does not support Constrained Baseline profile, force Baseline in this case.
|
||||
// libx264, h264_{qsv,nvenc,rkmpp} does not support Constrained Baseline profile, force Baseline in this case.
|
||||
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
&& profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "baseline";
|
||||
}
|
||||
|
||||
// libx264, h264_qsv, h264_nvenc and h264_vaapi does not support Constrained High profile, force High in this case.
|
||||
// libx264, h264_{qsv,nvenc,vaapi,rkmpp} does not support Constrained High profile, force High in this case.
|
||||
if ((string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
|| string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
&& profile.Contains("high", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profile = "high";
|
||||
@ -2015,6 +2059,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
param += " -level " + level;
|
||||
}
|
||||
}
|
||||
else if (string.Equals(videoEncoder, "h264_rkmpp", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoEncoder, "hevc_rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -level " + level;
|
||||
}
|
||||
else if (!string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
param += " -level " + level;
|
||||
@ -2833,6 +2882,48 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return (outputWidth, outputHeight);
|
||||
}
|
||||
|
||||
public static bool IsScaleRatioSupported(
|
||||
int? videoWidth,
|
||||
int? videoHeight,
|
||||
int? requestedWidth,
|
||||
int? requestedHeight,
|
||||
int? requestedMaxWidth,
|
||||
int? requestedMaxHeight,
|
||||
double? maxScaleRatio)
|
||||
{
|
||||
var (outWidth, outHeight) = GetFixedOutputSize(
|
||||
videoWidth,
|
||||
videoHeight,
|
||||
requestedWidth,
|
||||
requestedHeight,
|
||||
requestedMaxWidth,
|
||||
requestedMaxHeight);
|
||||
|
||||
if (!videoWidth.HasValue
|
||||
|| !videoHeight.HasValue
|
||||
|| !outWidth.HasValue
|
||||
|| !outHeight.HasValue
|
||||
|| !maxScaleRatio.HasValue
|
||||
|| (maxScaleRatio.Value < 1.0f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var minScaleRatio = 1.0f / maxScaleRatio;
|
||||
var scaleRatioW = (double)outWidth / (double)videoWidth;
|
||||
var scaleRatioH = (double)outHeight / (double)videoHeight;
|
||||
|
||||
if (scaleRatioW < minScaleRatio
|
||||
|| scaleRatioW > maxScaleRatio
|
||||
|| scaleRatioH < minScaleRatio
|
||||
|| scaleRatioH > maxScaleRatio)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static string GetHwScaleFilter(
|
||||
string hwScaleSuffix,
|
||||
string videoFormat,
|
||||
@ -2877,7 +2968,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public static string GetCustomSwScaleFilter(
|
||||
public static string GetGraphicalSubPreProcessFilters(
|
||||
int? videoWidth,
|
||||
int? videoHeight,
|
||||
int? requestedWidth,
|
||||
@ -2897,7 +2988,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"scale=s={0}x{1}:flags=fast_bilinear",
|
||||
@"scale=-1:{1}:fast_bilinear,crop,pad=max({0}\,iw):max({1}\,ih):(ow-iw)/2:(oh-ih)/2:black@0,crop={0}:{1}",
|
||||
outWidth.Value,
|
||||
outHeight.Value);
|
||||
}
|
||||
@ -2913,7 +3004,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
int? requestedHeight,
|
||||
int? requestedMaxWidth,
|
||||
int? requestedMaxHeight,
|
||||
int? framerate)
|
||||
float? framerate)
|
||||
{
|
||||
var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
|
||||
var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture);
|
||||
@ -2932,7 +3023,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
"alphasrc=s={0}x{1}:r={2}:start='{3}'",
|
||||
outWidth.Value,
|
||||
outHeight.Value,
|
||||
framerate ?? 10,
|
||||
framerate ?? 25,
|
||||
reqTicks > 0 ? startTime : 0);
|
||||
}
|
||||
|
||||
@ -3340,9 +3431,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
else if (hasGraphicalSubs)
|
||||
{
|
||||
// [0:s]scale=s=1280x720
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
|
||||
@ -3504,15 +3594,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
// scale=s=1280x720,format=yuva420p,hwupload
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=yuva420p");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
// alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=yuva420p");
|
||||
@ -3527,8 +3619,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
}
|
||||
@ -3702,15 +3794,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
// scale=s=1280x720,format=yuva420p,hwupload
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=yuva420p");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
// alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=yuva420p");
|
||||
@ -3727,8 +3821,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
}
|
||||
@ -3938,16 +4032,18 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
// scale,format=bgra,hwupload
|
||||
// overlay_qsv can handle overlay scaling,
|
||||
// add a dummy scale filter to pair with -canvas_size.
|
||||
subFilters.Add("scale=flags=fast_bilinear");
|
||||
// overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=bgra");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
// alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=bgra");
|
||||
@ -3973,8 +4069,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
}
|
||||
@ -4158,12 +4254,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
subFilters.Add("scale=flags=fast_bilinear");
|
||||
// overlay_qsv can handle overlay scaling, setup a smaller height to reduce transfer overhead
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=bgra");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=bgra");
|
||||
@ -4189,8 +4290,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
}
|
||||
@ -4425,12 +4526,17 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
subFilters.Add("scale=flags=fast_bilinear");
|
||||
// overlay_vaapi can handle overlay scaling, setup a smaller height to reduce transfer overhead
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, 1080);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=bgra");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=bgra");
|
||||
@ -4454,8 +4560,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
|
||||
if (isVaapiEncoder)
|
||||
@ -4599,14 +4705,16 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
// scale=s=1280x720,format=bgra,hwupload
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=bgra");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=bgra");
|
||||
@ -4815,8 +4923,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subSwScaleFilter);
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
|
||||
if (isVaapiEncoder)
|
||||
@ -4898,6 +5006,237 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter of Rockchip RKMPP/RKRGA filter chain.
|
||||
/// </summary>
|
||||
/// <param name="state">Encoding state.</param>
|
||||
/// <param name="options">Encoding options.</param>
|
||||
/// <param name="vidEncoder">Video encoder to use.</param>
|
||||
/// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
|
||||
public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFilterChain(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions options,
|
||||
string vidEncoder)
|
||||
{
|
||||
if (!string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return (null, null, null);
|
||||
}
|
||||
|
||||
var isLinux = OperatingSystem.IsLinux();
|
||||
var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
|
||||
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
|
||||
var isSwEncoder = !vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
|
||||
var isRkmppOclSupported = isLinux && IsRkmppFullSupported() && IsOpenclFullSupported();
|
||||
|
||||
if ((isSwDecoder && isSwEncoder)
|
||||
|| !isRkmppOclSupported
|
||||
|| !_mediaEncoder.SupportsFilter("alphasrc"))
|
||||
{
|
||||
return GetSwVidFilterChain(state, options, vidEncoder);
|
||||
}
|
||||
|
||||
// prefered rkmpp + rkrga + opencl filters pipeline
|
||||
if (isRkmppOclSupported)
|
||||
{
|
||||
return GetRkmppVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
|
||||
}
|
||||
|
||||
return (null, null, null);
|
||||
}
|
||||
|
||||
public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetRkmppVidFiltersPrefered(
|
||||
EncodingJobInfo state,
|
||||
EncodingOptions options,
|
||||
string vidDecoder,
|
||||
string vidEncoder)
|
||||
{
|
||||
var inW = state.VideoStream?.Width;
|
||||
var inH = state.VideoStream?.Height;
|
||||
var reqW = state.BaseRequest.Width;
|
||||
var reqH = state.BaseRequest.Height;
|
||||
var reqMaxW = state.BaseRequest.MaxWidth;
|
||||
var reqMaxH = state.BaseRequest.MaxHeight;
|
||||
var threeDFormat = state.MediaSource.Video3DFormat;
|
||||
|
||||
var isRkmppDecoder = vidDecoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
|
||||
var isRkmppEncoder = vidEncoder.Contains("rkmpp", StringComparison.OrdinalIgnoreCase);
|
||||
var isSwDecoder = !isRkmppDecoder;
|
||||
var isSwEncoder = !isRkmppEncoder;
|
||||
var isDrmInDrmOut = isRkmppDecoder && isRkmppEncoder;
|
||||
|
||||
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
|
||||
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
|
||||
var doDeintH2645 = doDeintH264 || doDeintHevc;
|
||||
var doOclTonemap = IsHwTonemapAvailable(state, options);
|
||||
|
||||
var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
|
||||
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
|
||||
var hasAssSubs = hasSubs
|
||||
&& (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
/* Make main filters for video stream */
|
||||
var mainFilters = new List<string>();
|
||||
|
||||
mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
|
||||
|
||||
if (isSwDecoder)
|
||||
{
|
||||
// INPUT sw surface(memory)
|
||||
// sw deint
|
||||
if (doDeintH2645)
|
||||
{
|
||||
var swDeintFilter = GetSwDeinterlaceFilter(state, options);
|
||||
mainFilters.Add(swDeintFilter);
|
||||
}
|
||||
|
||||
var outFormat = doOclTonemap ? "yuv420p10le" : (hasGraphicalSubs ? "yuv420p" : "nv12");
|
||||
var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
|
||||
if (!string.IsNullOrEmpty(swScaleFilter))
|
||||
{
|
||||
swScaleFilter += ":flags=fast_bilinear";
|
||||
}
|
||||
|
||||
// sw scale
|
||||
mainFilters.Add(swScaleFilter);
|
||||
mainFilters.Add("format=" + outFormat);
|
||||
|
||||
// keep video at memory except ocl tonemap,
|
||||
// since the overhead caused by hwupload >>> using sw filter.
|
||||
// sw => hw
|
||||
if (doOclTonemap)
|
||||
{
|
||||
mainFilters.Add("hwupload=derive_device=opencl");
|
||||
}
|
||||
}
|
||||
else if (isRkmppDecoder)
|
||||
{
|
||||
// INPUT rkmpp/drm surface(gem/dma-heap)
|
||||
|
||||
var isFullAfbcPipeline = isDrmInDrmOut && !doOclTonemap;
|
||||
var outFormat = doOclTonemap ? "p010" : "nv12";
|
||||
var hwScaleFilter = GetHwScaleFilter("rkrga", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
var hwScaleFilter2 = GetHwScaleFilter("rkrga", string.Empty, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
|
||||
if (!hasSubs
|
||||
|| !isFullAfbcPipeline
|
||||
|| !string.IsNullOrEmpty(hwScaleFilter2))
|
||||
{
|
||||
// try enabling AFBC to save DDR bandwidth
|
||||
if (!string.IsNullOrEmpty(hwScaleFilter) && isFullAfbcPipeline)
|
||||
{
|
||||
hwScaleFilter += ":afbc=1";
|
||||
}
|
||||
|
||||
// hw scale
|
||||
mainFilters.Add(hwScaleFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (doOclTonemap && isRkmppDecoder)
|
||||
{
|
||||
// map from rkmpp/drm to opencl via drm-opencl interop.
|
||||
mainFilters.Add("hwmap=derive_device=opencl:mode=read");
|
||||
}
|
||||
|
||||
// ocl tonemap
|
||||
if (doOclTonemap)
|
||||
{
|
||||
var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
|
||||
// enable tradeoffs for performance
|
||||
if (!string.IsNullOrEmpty(tonemapFilter))
|
||||
{
|
||||
tonemapFilter += ":tradeoff=1";
|
||||
}
|
||||
|
||||
mainFilters.Add(tonemapFilter);
|
||||
}
|
||||
|
||||
var memoryOutput = false;
|
||||
var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
|
||||
if ((isRkmppDecoder && isSwEncoder) || isUploadForOclTonemap)
|
||||
{
|
||||
memoryOutput = true;
|
||||
|
||||
// OUTPUT nv12 surface(memory)
|
||||
mainFilters.Add("hwdownload");
|
||||
mainFilters.Add("format=nv12");
|
||||
}
|
||||
|
||||
// OUTPUT nv12 surface(memory)
|
||||
if (isSwDecoder && isRkmppEncoder)
|
||||
{
|
||||
memoryOutput = true;
|
||||
}
|
||||
|
||||
if (memoryOutput)
|
||||
{
|
||||
// text subtitles
|
||||
if (hasTextSubs)
|
||||
{
|
||||
var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
|
||||
mainFilters.Add(textSubtitlesFilter);
|
||||
}
|
||||
}
|
||||
|
||||
if (isDrmInDrmOut)
|
||||
{
|
||||
if (doOclTonemap)
|
||||
{
|
||||
// OUTPUT drm(nv12) surface(gem/dma-heap)
|
||||
// reverse-mapping via drm-opencl interop.
|
||||
mainFilters.Add("hwmap=derive_device=rkmpp:mode=write:reverse=1");
|
||||
mainFilters.Add("format=drm_prime");
|
||||
}
|
||||
}
|
||||
|
||||
/* Make sub and overlay filters for subtitle stream */
|
||||
var subFilters = new List<string>();
|
||||
var overlayFilters = new List<string>();
|
||||
if (isDrmInDrmOut)
|
||||
{
|
||||
if (hasSubs)
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
subFilters.Add("format=bgra");
|
||||
}
|
||||
else if (hasTextSubs)
|
||||
{
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
var subFramerate = hasAssSubs ? Math.Min(framerate ?? 25, 60) : 10;
|
||||
|
||||
// alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
|
||||
var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, subFramerate);
|
||||
var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
|
||||
subFilters.Add(alphaSrcFilter);
|
||||
subFilters.Add("format=bgra");
|
||||
subFilters.Add(subTextSubtitlesFilter);
|
||||
}
|
||||
|
||||
subFilters.Add("hwupload=derive_device=rkmpp");
|
||||
|
||||
// try enabling AFBC to save DDR bandwidth
|
||||
overlayFilters.Add("overlay_rkrga=eof_action=pass:repeatlast=0:format=nv12:afbc=1");
|
||||
}
|
||||
}
|
||||
else if (memoryOutput)
|
||||
{
|
||||
if (hasGraphicalSubs)
|
||||
{
|
||||
var subPreProcFilters = GetGraphicalSubPreProcessFilters(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
|
||||
subFilters.Add(subPreProcFilters);
|
||||
overlayFilters.Add("overlay=eof_action=pass:repeatlast=0");
|
||||
}
|
||||
}
|
||||
|
||||
return (mainFilters, subFilters, overlayFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameter of video processing filters.
|
||||
/// </summary>
|
||||
@ -4944,6 +5283,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
(mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
|
||||
}
|
||||
else if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
(mainFilters, subFilters, overlayFilters) = GetRkmppVidFilterChain(state, options, outputVideoCodec);
|
||||
}
|
||||
else
|
||||
{
|
||||
(mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
|
||||
@ -5075,18 +5418,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv422p", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv422p10le", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv422p12le", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return 12;
|
||||
@ -5139,7 +5485,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|| string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return null;
|
||||
// One exception is that RKMPP decoder can handle H.264 High 10.
|
||||
if (!(string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||
@ -5166,6 +5517,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
{
|
||||
return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth);
|
||||
}
|
||||
|
||||
if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetRkmppVidDecoder(state, options, videoStream, bitDepth);
|
||||
}
|
||||
}
|
||||
|
||||
var whichCodec = videoStream.Codec;
|
||||
@ -5231,6 +5587,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.Equals(decoderSuffix, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return isCodecAvailable ? (" -c:v " + decoderName) : null;
|
||||
}
|
||||
|
||||
@ -5253,6 +5614,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
|
||||
var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
|
||||
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
|
||||
var isRkmppSupported = isLinux && IsRkmppFullSupported();
|
||||
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var ffmpegVersion = _mediaEncoder.EncoderVersion;
|
||||
@ -5355,6 +5717,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty);
|
||||
}
|
||||
|
||||
// Rockchip rkmpp
|
||||
if (string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase)
|
||||
&& isRkmppSupported
|
||||
&& isCodecAvailable)
|
||||
{
|
||||
return " -hwaccel rkmpp" + (outputHwSurface ? " -hwaccel_output_format drm_prime" : string.Empty);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -5661,6 +6031,102 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetRkmppVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
|
||||
{
|
||||
var isLinux = OperatingSystem.IsLinux();
|
||||
|
||||
if (!isLinux
|
||||
|| !string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var inW = state.VideoStream?.Width;
|
||||
var inH = state.VideoStream?.Height;
|
||||
var reqW = state.BaseRequest.Width;
|
||||
var reqH = state.BaseRequest.Height;
|
||||
var reqMaxW = state.BaseRequest.MaxWidth;
|
||||
var reqMaxH = state.BaseRequest.MaxHeight;
|
||||
|
||||
// rkrga RGA2e supports range from 1/16 to 16
|
||||
if (!IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 16.0f))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var isRkmppOclSupported = IsRkmppFullSupported() && IsOpenclFullSupported();
|
||||
var hwSurface = isRkmppOclSupported
|
||||
&& _mediaEncoder.SupportsFilter("alphasrc");
|
||||
|
||||
// rkrga RGA3 supports range from 1/8 to 8
|
||||
var isAfbcSupported = hwSurface && IsScaleRatioSupported(inW, inH, reqW, reqH, reqMaxW, reqMaxH, 8.0f);
|
||||
|
||||
// TODO: add more 8/10bit and 4:2:2 formats for Rkmpp after finishing the ffcheck tool
|
||||
var is8bitSwFormatsRkmpp = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals("yuvj420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||
var is10bitSwFormatsRkmpp = string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
|
||||
var is8_10bitSwFormatsRkmpp = is8bitSwFormatsRkmpp || is10bitSwFormatsRkmpp;
|
||||
|
||||
// nv15 and nv20 are bit-stream only formats
|
||||
if (is10bitSwFormatsRkmpp && !hwSurface)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (is8bitSwFormatsRkmpp)
|
||||
{
|
||||
if (string.Equals(videoStream.Codec, "mpeg1video", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetHwaccelType(state, options, "mpeg1video", bitDepth, hwSurface);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
|
||||
}
|
||||
}
|
||||
|
||||
if (is8_10bitSwFormatsRkmpp)
|
||||
{
|
||||
if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var accelType = GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
|
||||
return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var accelType = GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
|
||||
return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var accelType = GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
|
||||
return accelType + ((!string.IsNullOrEmpty(accelType) && isAfbcSupported) ? " -afbc rga" : string.Empty);
|
||||
}
|
||||
|
||||
if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of threads.
|
||||
/// </summary>
|
||||
|
@ -111,7 +111,8 @@ namespace MediaBrowser.Controller.Session
|
||||
/// Reports the session ended.
|
||||
/// </summary>
|
||||
/// <param name="sessionId">The session identifier.</param>
|
||||
void ReportSessionEnded(string sessionId);
|
||||
/// <returns>Task.</returns>
|
||||
ValueTask ReportSessionEnded(string sessionId);
|
||||
|
||||
/// <summary>
|
||||
/// Sends the general command.
|
||||
|
@ -19,7 +19,7 @@ namespace MediaBrowser.Controller.Session
|
||||
/// <summary>
|
||||
/// Class SessionInfo.
|
||||
/// </summary>
|
||||
public sealed class SessionInfo : IAsyncDisposable, IDisposable
|
||||
public sealed class SessionInfo : IAsyncDisposable
|
||||
{
|
||||
// 1 second
|
||||
private const long ProgressIncrement = 10000000;
|
||||
@ -374,8 +374,7 @@ namespace MediaBrowser.Controller.Session
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
@ -386,30 +385,17 @@ namespace MediaBrowser.Controller.Session
|
||||
|
||||
foreach (var controller in controllers)
|
||||
{
|
||||
if (controller is IDisposable disposable)
|
||||
if (controller is IAsyncDisposable disposableAsync)
|
||||
{
|
||||
_logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
|
||||
await disposableAsync.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
else if (controller is IDisposable disposable)
|
||||
{
|
||||
_logger.LogDebug("Disposing session controller synchronously {TypeName}", disposable.GetType().Name);
|
||||
disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
_disposed = true;
|
||||
|
||||
StopAutomaticProgress();
|
||||
|
||||
var controllers = SessionControllers.ToList();
|
||||
|
||||
foreach (var controller in controllers)
|
||||
{
|
||||
if (controller is IAsyncDisposable disposableAsync)
|
||||
{
|
||||
_logger.LogDebug("Disposing session controller asynchronously {TypeName}", disposableAsync.GetType().Name);
|
||||
await disposableAsync.DisposeAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,15 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"mpeg4_cuvid",
|
||||
"vp8_cuvid",
|
||||
"vp9_cuvid",
|
||||
"av1_cuvid"
|
||||
"av1_cuvid",
|
||||
"h264_rkmpp",
|
||||
"hevc_rkmpp",
|
||||
"mpeg1_rkmpp",
|
||||
"mpeg2_rkmpp",
|
||||
"mpeg4_rkmpp",
|
||||
"vp8_rkmpp",
|
||||
"vp9_rkmpp",
|
||||
"av1_rkmpp"
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredEncoders = new[]
|
||||
@ -82,7 +90,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"av1_vaapi",
|
||||
"h264_v4l2m2m",
|
||||
"h264_videotoolbox",
|
||||
"hevc_videotoolbox"
|
||||
"hevc_videotoolbox",
|
||||
"h264_rkmpp",
|
||||
"hevc_rkmpp"
|
||||
};
|
||||
|
||||
private static readonly string[] _requiredFilters = new[]
|
||||
@ -116,9 +126,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"libplacebo",
|
||||
"scale_vulkan",
|
||||
"overlay_vulkan",
|
||||
"hwupload_vaapi",
|
||||
// videotoolbox
|
||||
"yadif_videotoolbox"
|
||||
"yadif_videotoolbox",
|
||||
// rkrga
|
||||
"scale_rkrga",
|
||||
"vpp_rkrga",
|
||||
"overlay_rkrga"
|
||||
};
|
||||
|
||||
private static readonly Dictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
|
||||
|
@ -904,8 +904,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
bool ranToCompletion = false;
|
||||
|
||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
using (await _thumbnailResourcePool.LockAsync(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
StartProcess(processWrapper);
|
||||
|
||||
@ -959,10 +958,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
StopProcess(processWrapper, 1000);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_thumbnailResourcePool.Release();
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
|
||||
|
||||
|
@ -742,6 +742,10 @@ namespace MediaBrowser.MediaEncoding.Probing
|
||||
stream.LocalizedExternal = _localization.GetLocalizedString("External");
|
||||
stream.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
|
||||
|
||||
// Graphical subtitle may have width and height info
|
||||
stream.Width = streamInfo.Width;
|
||||
stream.Height = streamInfo.Height;
|
||||
|
||||
if (string.IsNullOrEmpty(stream.Title))
|
||||
{
|
||||
// mp4 missing track title workaround: fall back to handler_name if populated and not the default "SubtitleHandler"
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@ -18,7 +19,6 @@ using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -198,36 +198,11 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
{
|
||||
if (!subtitleStream.IsExternal || subtitleStream.Path.EndsWith(".mks", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string outputFormat;
|
||||
string outputCodec;
|
||||
await ExtractAllTextSubtitles(mediaSource, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(subtitleStream.Codec, "srt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Extract
|
||||
outputCodec = "copy";
|
||||
outputFormat = subtitleStream.Codec;
|
||||
}
|
||||
else if (string.Equals(subtitleStream.Codec, "subrip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Extract
|
||||
outputCodec = "copy";
|
||||
outputFormat = "srt";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Extract
|
||||
outputCodec = "srt";
|
||||
outputFormat = "srt";
|
||||
}
|
||||
|
||||
// Extract
|
||||
var outputFormat = GetTextSubtitleFormat(subtitleStream);
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + outputFormat);
|
||||
|
||||
await ExtractTextSubtitle(mediaSource, subtitleStream, outputCodec, outputPath, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return new SubtitleInfo()
|
||||
{
|
||||
Path = outputPath,
|
||||
@ -453,6 +428,203 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
_logger.LogInformation("ffmpeg subtitle conversion succeeded for {Path}", inputPath);
|
||||
}
|
||||
|
||||
private string GetTextSubtitleFormat(MediaStream subtitleStream)
|
||||
{
|
||||
if (string.Equals(subtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(subtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return subtitleStream.Codec;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "srt";
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCodecCopyable(string codec)
|
||||
{
|
||||
return string.Equals(codec, "ass", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "ssa", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "srt", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "subrip", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts all text subtitles.
|
||||
/// </summary>
|
||||
/// <param name="mediaSource">The mediaSource.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task ExtractAllTextSubtitles(MediaSourceInfo mediaSource, CancellationToken cancellationToken)
|
||||
{
|
||||
var locks = new List<AsyncKeyedLockReleaser<string>>();
|
||||
var extractableStreams = new List<MediaStream>();
|
||||
|
||||
try
|
||||
{
|
||||
var subtitleStreams = mediaSource.MediaStreams
|
||||
.Where(stream => stream.IsTextSubtitleStream && stream.SupportsExternalStream);
|
||||
|
||||
foreach (var subtitleStream in subtitleStreams)
|
||||
{
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
|
||||
|
||||
var @lock = _semaphoreLocks.GetOrAdd(outputPath);
|
||||
await @lock.SemaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (File.Exists(outputPath))
|
||||
{
|
||||
@lock.Dispose();
|
||||
continue;
|
||||
}
|
||||
|
||||
locks.Add(@lock);
|
||||
extractableStreams.Add(subtitleStream);
|
||||
}
|
||||
|
||||
if (extractableStreams.Count > 0)
|
||||
{
|
||||
await ExtractAllTextSubtitlesInternal(mediaSource, extractableStreams, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Unable to get streams for File:{File}", mediaSource.Path);
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var @lock in locks)
|
||||
{
|
||||
@lock.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractAllTextSubtitlesInternal(
|
||||
MediaSourceInfo mediaSource,
|
||||
List<MediaStream> subtitleStreams,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputPath = mediaSource.Path;
|
||||
var outputPaths = new List<string>();
|
||||
var args = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"-i {0} -copyts",
|
||||
inputPath);
|
||||
|
||||
foreach (var subtitleStream in subtitleStreams)
|
||||
{
|
||||
var outputPath = GetSubtitleCachePath(mediaSource, subtitleStream.Index, "." + GetTextSubtitleFormat(subtitleStream));
|
||||
var outputCodec = IsCodecCopyable(subtitleStream.Codec) ? "copy" : "srt";
|
||||
var streamIndex = EncodingHelper.FindIndex(mediaSource.MediaStreams, subtitleStream);
|
||||
|
||||
if (streamIndex == -1)
|
||||
{
|
||||
_logger.LogError("Cannot find subtitle stream index for {InputPath} ({Index}), skipping this stream", inputPath, subtitleStream.Index);
|
||||
continue;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? throw new FileNotFoundException($"Calculated path ({outputPath}) is not valid."));
|
||||
|
||||
outputPaths.Add(outputPath);
|
||||
args += string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
" -map 0:{0} -an -vn -c:s {1} \"{2}\"",
|
||||
streamIndex,
|
||||
outputCodec,
|
||||
outputPath);
|
||||
}
|
||||
|
||||
int exitCode;
|
||||
|
||||
using (var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = args,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
})
|
||||
{
|
||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting ffmpeg");
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
|
||||
exitCode = process.ExitCode;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
process.Kill(true);
|
||||
exitCode = -1;
|
||||
}
|
||||
}
|
||||
|
||||
var failed = false;
|
||||
|
||||
if (exitCode == -1)
|
||||
{
|
||||
failed = true;
|
||||
|
||||
foreach (var outputPath in outputPaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("Deleting extracted subtitle due to failure: {Path}", outputPath);
|
||||
_fileSystem.DeleteFile(outputPath);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error deleting extracted subtitle {Path}", outputPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var outputPath in outputPaths)
|
||||
{
|
||||
if (!File.Exists(outputPath))
|
||||
{
|
||||
_logger.LogError("ffmpeg subtitle extraction failed for {InputPath} to {OutputPath}", inputPath, outputPath);
|
||||
failed = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (outputPath.EndsWith("ass", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
await SetAssFont(outputPath, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
throw new FfmpegException(
|
||||
string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0}", inputPath));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extracts the text subtitle.
|
||||
/// </summary>
|
||||
|
@ -11,6 +11,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AsyncKeyedLock;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
@ -401,7 +402,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable
|
||||
|
||||
if (state.VideoRequest is not null && !EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
|
||||
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
|
||||
if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
|
||||
{
|
||||
this.OnTranscodeFailedToStart(outputPath, transcodingJobType, state);
|
||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Extensions;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
@ -1536,7 +1537,7 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
private static void ValidateMediaOptions(MediaOptions options, bool isMediaSource)
|
||||
{
|
||||
if (options.ItemId.Equals(default))
|
||||
if (options.ItemId.IsEmpty())
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(options.DeviceId);
|
||||
}
|
||||
|
@ -13,8 +13,6 @@ namespace MediaBrowser.Model.IO
|
||||
|
||||
Task CopyToAsync(Stream source, Stream destination, int bufferSize, int emptyReadLimit, CancellationToken cancellationToken);
|
||||
|
||||
Task CopyToAsync(Stream source, Stream destination, long copyLength, CancellationToken cancellationToken);
|
||||
|
||||
Task CopyUntilCancelled(Stream source, Stream target, int bufferSize, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Model.LiveTv
|
||||
{
|
||||
public enum LiveTvTunerStatus
|
||||
{
|
||||
Available = 0,
|
||||
Disabled = 1,
|
||||
RecordingTv = 2,
|
||||
LiveTv = 3
|
||||
}
|
||||
}
|
@ -33,6 +33,11 @@
|
||||
/// <summary>
|
||||
/// Video ToolBox.
|
||||
/// </summary>
|
||||
VideoToolBox = 5
|
||||
VideoToolBox = 5,
|
||||
|
||||
/// <summary>
|
||||
/// Rockchip Media Process Platform (RKMPP).
|
||||
/// </summary>
|
||||
RKMPP = 6
|
||||
}
|
||||
}
|
||||
|
@ -706,7 +706,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
{
|
||||
BaseItem? referenceItem = null;
|
||||
|
||||
if (!searchInfo.ItemId.Equals(default))
|
||||
if (!searchInfo.ItemId.IsEmpty())
|
||||
{
|
||||
referenceItem = _libraryManager.GetItemById(searchInfo.ItemId);
|
||||
}
|
||||
@ -944,7 +944,7 @@ namespace MediaBrowser.Providers.Manager
|
||||
public void QueueRefresh(Guid itemId, MetadataRefreshOptions options, RefreshPriority priority)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(itemId);
|
||||
if (itemId.Equals(default))
|
||||
if (itemId.IsEmpty())
|
||||
{
|
||||
throw new ArgumentException("Guid can't be empty", nameof(itemId));
|
||||
}
|
||||
|
@ -460,10 +460,28 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
||||
var trailer = reader.ReadNormalizedString();
|
||||
if (!string.IsNullOrEmpty(trailer))
|
||||
{
|
||||
item.AddTrailerUrl(trailer.Replace(
|
||||
"plugin://plugin.video.youtube/?action=play_video&videoid=",
|
||||
BaseNfoSaver.YouTubeWatchUrl,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
if (trailer.StartsWith("plugin://plugin.video.youtube/?action=play_video&videoid=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Deprecated format
|
||||
item.AddTrailerUrl(trailer.Replace(
|
||||
"plugin://plugin.video.youtube/?action=play_video&videoid=",
|
||||
BaseNfoSaver.YouTubeWatchUrl,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var suggestedUrl = trailer.Replace(
|
||||
"plugin://plugin.video.youtube/?action=play_video&videoid=",
|
||||
"plugin://plugin.video.youtube/play/?video_id=",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
Logger.LogWarning("Trailer URL uses a deprecated format : {Url}. Using {NewUrl} instead is advised.", trailer, suggestedUrl);
|
||||
}
|
||||
else if (trailer.StartsWith("plugin://plugin.video.youtube/play/?video_id=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Proper format
|
||||
item.AddTrailerUrl(trailer.Replace(
|
||||
"plugin://plugin.video.youtube/play/?video_id=",
|
||||
BaseNfoSaver.YouTubeWatchUrl,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
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