Merge branch 'master' into network-rewrite

This commit is contained in:
Shadowghost 2023-05-09 15:25:41 +02:00
commit 6cc1203c1b
154 changed files with 1724 additions and 1078 deletions

View File

@ -30,9 +30,9 @@ body:
label: Jellyfin Version label: Jellyfin Version
description: What version of Jellyfin are you running? description: What version of Jellyfin are you running?
options: options:
- 10.8.0 - 10.8.z
- 10.8.9
- 10.7.7 - 10.7.7
- 10.7.z
- 10.6.4 - 10.6.4
- Other - Other
validations: validations:
@ -47,13 +47,15 @@ body:
label: Environment label: Environment
description: | description: |
Examples: Examples:
- **OS**: [e.g. Debian, Windows] - **OS**: [e.g. Debian 11, Windows 10]
- **Linux Kernel**: [e.g. none, 5.15, 6.1, etc.]
- **Virtualization**: [e.g. Docker, KVM, LXC] - **Virtualization**: [e.g. Docker, KVM, LXC]
- **Clients**: [Browser, Android, Fire Stick, etc.] - **Clients**: [Browser, Android, Fire Stick, etc.]
- **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13] - **Browser**: [e.g. Firefox 91, Chrome 93, Safari 13]
- **FFmpeg Version**: [e.g. 4.3.2-Jellyfin] - **FFmpeg Version**: [e.g. 5.1.2-Jellyfin]
- **Playback**: [Direct Play, Remux, Direct Stream, Transcode] - **Playback**: [Direct Play, Remux, Direct Stream, Transcode]
- **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.] - **Hardware Acceleration**: [e.g. none, VAAPI, NVENC, etc.]
- **GPU Model**: [e.g. none, UHD630, GTX1050, etc.]
- **Installed Plugins**: [e.g. none, Fanart, Anime, etc.] - **Installed Plugins**: [e.g. none, Fanart, Anime, etc.]
- **Reverse Proxy**: [e.g. none, nginx, apache, etc.] - **Reverse Proxy**: [e.g. none, nginx, apache, etc.]
- **Base URL**: [e.g. none, yes: /example] - **Base URL**: [e.g. none, yes: /example]
@ -61,12 +63,14 @@ body:
- **Storage**: [e.g. local, NFS, cloud] - **Storage**: [e.g. local, NFS, cloud]
value: | value: |
- OS: - OS:
- Linux Kernel:
- Virtualization: - Virtualization:
- Clients: - Clients:
- Browser: - Browser:
- FFmpeg Version: - FFmpeg Version:
- Playback Method: - Playback Method:
- Hardware Acceleration: - Hardware Acceleration:
- GPU Model:
- Plugins: - Plugins:
- Reverse Proxy: - Reverse Proxy:
- Base URL: - Base URL:
@ -84,8 +88,8 @@ body:
id: ffmpeg-logs id: ffmpeg-logs
attributes: attributes:
label: FFmpeg logs label: FFmpeg logs
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs. description: Please copy and paste recent FFmpeg log output. This can be found in Dashboard > Logs > FFmpeg*.log.
placeholder: It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg. placeholder: This field is mandatory for debugging hardware transcoding issues. It's important to include the specific codec details. If no FFmpeg logs appear, the file was Direct Played and did not use FFmpeg.
render: shell render: shell
- type: textarea - type: textarea
id: browserlogs id: browserlogs

View File

@ -19,6 +19,7 @@ jobs:
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}} if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with: with:
dirtyLabel: 'merge conflict' dirtyLabel: 'merge conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
repoToken: ${{ secrets.JF_BOT_TOKEN }} repoToken: ${{ secrets.JF_BOT_TOKEN }}
project: project:

View File

@ -20,18 +20,18 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # tag=v3 uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
with: with:
dotnet-version: '7.0.x' dotnet-version: '7.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2 uses: github/codeql-action/init@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2 uses: github/codeql-action/autobuild@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@168b99b3c22180941ae7dbdd5f5c9678ede476ba # v2 uses: github/codeql-action/analyze@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3

View File

@ -17,14 +17,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Notify as seen - name: Notify as seen
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }} comment-id: ${{ github.event.comment.id }}
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Notify as seen - name: Notify as seen
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ github.event.comment != null }} if: ${{ github.event.comment != null }}
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
@ -51,14 +51,14 @@ jobs:
reactions: eyes reactions: eyes
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
- name: Notify as running - name: Notify as running
id: comment_running id: comment_running
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ github.event.comment != null }} if: ${{ github.event.comment != null }}
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
@ -93,7 +93,7 @@ jobs:
exit ${retcode} exit ${retcode}
- name: Notify with result success - name: Notify with result success
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ github.event.comment != null && success() }} if: ${{ github.event.comment != null && success() }}
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
@ -108,7 +108,7 @@ jobs:
reactions: hooray reactions: hooray
- name: Notify with result failure - name: Notify with result failure
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ github.event.comment != null && failure() }} if: ${{ github.event.comment != null && failure() }}
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}

View File

@ -14,18 +14,18 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # tag=v3 uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
with: with:
dotnet-version: '7.0.x' dotnet-version: '7.0.x'
- name: Generate openapi.json - name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json - name: Upload openapi.json
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: openapi-head name: openapi-head
retention-days: 14 retention-days: 14
@ -39,7 +39,7 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@24cb9080177205b6e8c946b17badbe402adc938f # v3 uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }} repository: ${{ github.event.pull_request.head.repo.full_name }}
@ -51,13 +51,13 @@ jobs:
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }}) ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/${{ github.head_ref }})
git checkout --progress --force $ANCESTOR_REF git checkout --progress --force $ANCESTOR_REF
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # tag=v3 uses: actions/setup-dotnet@607fce577a46308457984d59e4954e075820f10a # v3.0.3
with: with:
dotnet-version: '7.0.x' dotnet-version: '7.0.x'
- name: Generate openapi.json - name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests" run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json - name: Upload openapi.json
uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3 uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
with: with:
name: openapi-base name: openapi-base
retention-days: 14 retention-days: 14
@ -76,12 +76,12 @@ jobs:
- openapi-base - openapi-base
steps: steps:
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3 uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with: with:
name: openapi-head name: openapi-head
path: openapi-head path: openapi-head
- name: Download openapi-base - name: Download openapi-base
uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3 uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2
with: with:
name: openapi-base name: openapi-base
path: openapi-base path: openapi-base
@ -103,14 +103,14 @@ jobs:
body="${body//$'\r'/'%0D'}" body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body echo ::set-output name=body::$body
- name: Find difference comment - name: Find difference comment
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb # v2 uses: peter-evans/find-comment@a54c31d7fa095754bfef525c0c8e5e5674c4b4b1 # v2.4.0
id: find-comment id: find-comment
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
direction: last direction: last
body-includes: openapi-diff-workflow-comment body-includes: openapi-diff-workflow-comment
- name: Reply or edit difference comment (changed) - name: Reply or edit difference comment (changed)
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ steps.read-diff.outputs.body != '' }} if: ${{ steps.read-diff.outputs.body != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
@ -125,7 +125,7 @@ jobs:
</details> </details>
- name: Edit difference comment (unchanged) - name: Edit difference comment (unchanged)
uses: peter-evans/create-or-update-comment@67dcc547d311b736a8e6c5c236542148a47adc3d # v2 uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}

View File

@ -1,4 +1,4 @@
name: Issue Stale Check name: Stale Check
on: on:
schedule: schedule:
@ -7,12 +7,15 @@ on:
permissions: permissions:
issues: write issues: write
pull-requests: write
jobs: jobs:
stale: issues:
name: Check issues
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@6f05e4244c9a0b2ed3401882b05d701dd0a7289b # v7 - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
days-before-stale: 120 days-before-stale: 120
@ -28,3 +31,21 @@ jobs:
If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label. If you're the original submitter of this issue, please comment confirming if this issue still affects you in the latest release or master branch, or close the issue if it has been fixed. If you're another user also affected by this bug, please comment confirming so. Either action will remove the stale label.
This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html). This bot exists to prevent issues from becoming stale and forgotten. Jellyfin is always moving forward, and bugs are often fixed as side effects of other changes. We therefore ask that bug report authors remain vigilant about their issues to ensure they are closed if fixed, or re-confirmed - perhaps with fresh logs or reproduction examples - regularly. If you have any questions you can reach us on [Matrix or Social Media](https://docs.jellyfin.org/general/getting-help.html).
prs-conflicts:
name: Check PRs with merge conflicts
runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }}
steps:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with:
repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75
# The merge conflict action will remove the label when updated
remove-stale-when-updated: false
days-before-stale: -1
days-before-close: 90
days-before-issue-close: -1
stale-pr-label: merge conflict
close-pr-message: |-
This PR has been closed due to having unresolved merge conflicts.

View File

@ -14,23 +14,23 @@
<PackageVersion Include="BlurHashSharp" Version="1.2.0" /> <PackageVersion Include="BlurHashSharp" Version="1.2.0" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="3.2.0" /> <PackageVersion Include="coverlet.collector" Version="3.2.0" />
<PackageVersion Include="Diacritics" Version="3.3.14" /> <PackageVersion Include="Diacritics" Version="3.3.18" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.6" /> <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.1" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" /> <PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.11" /> <PackageVersion Include="libse" Version="3.6.11" />
<PackageVersion Include="LrcParser" Version="2023.308.0" /> <PackageVersion Include="LrcParser" Version="2023.308.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" /> <PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.4" /> <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.5" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.4" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.5" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.4" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.4" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.4" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.4" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
@ -39,8 +39,8 @@
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.4" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.5" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.4" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.5" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
@ -65,7 +65,7 @@
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageVersion Include="Serilog.Sinks.Graylog" Version="2.3.0" /> <PackageVersion Include="Serilog.Sinks.Graylog" Version="2.3.0" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" /> <PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.0.1" /> <PackageVersion Include="SharpFuzz" Version="2.0.2" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.3" />
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" /> <PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.3" /> <PackageVersion Include="SkiaSharp" Version="2.88.3" />

View File

@ -10,6 +10,7 @@ using System.Text;
using System.Xml; using System.Xml;
using Emby.Dlna.ContentDirectory; using Emby.Dlna.ContentDirectory;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -870,11 +871,11 @@ namespace Emby.Dlna.Didl
var types = new[] var types = new[]
{ {
PersonType.Director, PersonKind.Director,
PersonType.Writer, PersonKind.Writer,
PersonType.Producer, PersonKind.Producer,
PersonType.Composer, PersonKind.Composer,
"creator" PersonKind.Creator
}; };
// Seeing some LG models locking up due content with large lists of people // Seeing some LG models locking up due content with large lists of people
@ -888,10 +889,13 @@ namespace Emby.Dlna.Didl
foreach (var actor in people) foreach (var actor in people)
{ {
var type = types.FirstOrDefault(i => string.Equals(i, actor.Type, StringComparison.OrdinalIgnoreCase) || string.Equals(i, actor.Role, StringComparison.OrdinalIgnoreCase)) var type = types.FirstOrDefault(i => i == actor.Type || string.Equals(actor.Role, i.ToString(), StringComparison.OrdinalIgnoreCase));
?? PersonType.Actor; if (type == PersonKind.Unknown)
{
type = PersonKind.Actor;
}
AddValue(writer, "upnp", type.ToLowerInvariant(), actor.Name, NsUpnp); AddValue(writer, "upnp", type.ToString().ToLowerInvariant(), actor.Name, NsUpnp);
} }
} }

View File

@ -116,7 +116,7 @@ namespace Emby.Dlna.PlayTo
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString); return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
} }
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, string commandParameter = "") public string BuildPost(ServiceAction action, string xmlNamespace, object value, string commandParameter = "")
{ {
var stateString = string.Empty; var stateString = string.Empty;
@ -137,10 +137,10 @@ namespace Emby.Dlna.PlayTo
} }
} }
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
} }
public string BuildPost(ServiceAction action, string xmlNamesapce, object value, Dictionary<string, string> dictionary) public string BuildPost(ServiceAction action, string xmlNamespace, object value, Dictionary<string, string> dictionary)
{ {
var stateString = string.Empty; var stateString = string.Empty;
@ -150,9 +150,9 @@ namespace Emby.Dlna.PlayTo
{ {
stateString += BuildArgumentXml(arg, "0"); stateString += BuildArgumentXml(arg, "0");
} }
else if (dictionary.ContainsKey(arg.Name)) else if (dictionary.TryGetValue(arg.Name, out var argValue))
{ {
stateString += BuildArgumentXml(arg, dictionary[arg.Name]); stateString += BuildArgumentXml(arg, argValue);
} }
else else
{ {
@ -160,7 +160,7 @@ namespace Emby.Dlna.PlayTo
} }
} }
return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamesapce, stateString); return string.Format(CultureInfo.InvariantCulture, CommandBase, action.Name, xmlNamespace, stateString);
} }
private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "") private string BuildArgumentXml(Argument argument, string? value, string commandParameter = "")

View File

@ -141,8 +141,7 @@ namespace Emby.Naming.Common
VideoFileStackingRules = new[] VideoFileStackingRules = new[]
{ {
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true), new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[0-9]+)[\)\]]?(?:\.[^.]+)?$", true),
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false), new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]+)[\(\[]?(?<parttype>cd|dvd|part|pt|dis[ck])[ _.-]*(?<number>[a-d])[\)\]]?(?:\.[^.]+)?$", false)
new FileStackRule(@"^(?<filename>.*?)(?:(?<=[\]\)\}])|[ _.-]?)(?<number>[a-d])(?:\.[^.]+)?$", false)
}; };
CleanDateTimes = new[] CleanDateTimes = new[]
@ -157,7 +156,8 @@ namespace Emby.Naming.Common
@"^(?<cleaned>.+?)(\[.*\])", @"^(?<cleaned>.+?)(\[.*\])",
@"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)", @"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)",
@"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)", @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)",
@"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$" @"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$",
@"^\s*(?<cleaned>.+?)(([-._ ](trailer|sample))|-(scene|clip|behindthescenes|deleted|deletedscene|featurette|short|interview|other|extra))$"
}; };
SubtitleFileExtensions = new[] SubtitleFileExtensions = new[]
@ -270,7 +270,6 @@ namespace Emby.Naming.Common
".sfx", ".sfx",
".shn", ".shn",
".sid", ".sid",
".spc",
".stm", ".stm",
".strm", ".strm",
".ult", ".ult",

View File

@ -87,8 +87,7 @@ namespace Emby.Naming.Video
name = cleanDateTimeResult.Name; name = cleanDateTimeResult.Name;
year = cleanDateTimeResult.Year; year = cleanDateTimeResult.Year;
if (extraResult.ExtraType is null if (TryCleanString(name, namingOptions, out var newName))
&& TryCleanString(name, namingOptions, out var newName))
{ {
name = newName; name = newName;
} }

View File

@ -627,6 +627,9 @@ namespace Emby.Server.Implementations
} }
} }
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize();
((SqliteUserDataRepository)Resolve<IUserDataRepository>()).Initialize();
var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>(); var localizationManager = (LocalizationManager)Resolve<ILocalizationManager>();
await localizationManager.LoadAll().ConfigureAwait(false); await localizationManager.LoadAll().ConfigureAwait(false);
@ -634,9 +637,6 @@ namespace Emby.Server.Implementations
SetStaticProperties(); SetStaticProperties();
var userDataRepo = (SqliteUserDataRepository)Resolve<IUserDataRepository>();
((SqliteItemRepository)Resolve<IItemRepository>()).Initialize(userDataRepo, Resolve<IUserManager>());
FindParts(); FindParts();
} }

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@ -27,9 +26,19 @@ namespace Emby.Server.Implementations.Data
/// <summary> /// <summary>
/// Gets or sets the path to the DB file. /// Gets or sets the path to the DB file.
/// </summary> /// </summary>
/// <value>Path to the DB file.</value>
protected string DbFilePath { get; set; } protected string DbFilePath { get; set; }
/// <summary>
/// Gets or sets the number of write connections to create.
/// </summary>
/// <value>Path to the DB file.</value>
protected int WriteConnectionsCount { get; set; } = 1;
/// <summary>
/// Gets or sets the number of read connections to create.
/// </summary>
protected int ReadConnectionsCount { get; set; } = 1;
/// <summary> /// <summary>
/// Gets the logger. /// Gets the logger.
/// </summary> /// </summary>
@ -63,7 +72,7 @@ namespace Emby.Server.Implementations.Data
/// <summary> /// <summary>
/// Gets the locking mode. <see href="https://www.sqlite.org/pragma.html#pragma_locking_mode" />. /// Gets the locking mode. <see href="https://www.sqlite.org/pragma.html#pragma_locking_mode" />.
/// </summary> /// </summary>
protected virtual string LockingMode => "EXCLUSIVE"; protected virtual string LockingMode => "NORMAL";
/// <summary> /// <summary>
/// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />. /// Gets the journal mode. <see href="https://www.sqlite.org/pragma.html#pragma_journal_mode" />.
@ -88,7 +97,7 @@ namespace Emby.Server.Implementations.Data
/// </summary> /// </summary>
/// <value>The temp store mode.</value> /// <value>The temp store mode.</value>
/// <see cref="TempStoreMode"/> /// <see cref="TempStoreMode"/>
protected virtual TempStoreMode TempStore => TempStoreMode.Default; protected virtual TempStoreMode TempStore => TempStoreMode.Memory;
/// <summary> /// <summary>
/// Gets the synchronous mode. /// Gets the synchronous mode.
@ -101,63 +110,106 @@ namespace Emby.Server.Implementations.Data
/// Gets or sets the write lock. /// Gets or sets the write lock.
/// </summary> /// </summary>
/// <value>The write lock.</value> /// <value>The write lock.</value>
protected SemaphoreSlim WriteLock { get; set; } = new SemaphoreSlim(1, 1); protected ConnectionPool WriteConnections { get; set; }
/// <summary> /// <summary>
/// Gets or sets the write connection. /// Gets or sets the write connection.
/// </summary> /// </summary>
/// <value>The write connection.</value> /// <value>The write connection.</value>
protected SQLiteDatabaseConnection WriteConnection { get; set; } protected ConnectionPool ReadConnections { get; set; }
public virtual void Initialize()
{
WriteConnections = new ConnectionPool(WriteConnectionsCount, CreateWriteConnection);
ReadConnections = new ConnectionPool(ReadConnectionsCount, CreateReadConnection);
// Configuration and pragmas can affect VACUUM so it needs to be last.
using (var connection = GetConnection())
{
connection.Execute("VACUUM");
}
}
protected ManagedConnection GetConnection(bool readOnly = false) protected ManagedConnection GetConnection(bool readOnly = false)
{ => readOnly ? ReadConnections.GetConnection() : WriteConnections.GetConnection();
WriteLock.Wait();
if (WriteConnection is not null)
{
return new ManagedConnection(WriteConnection, WriteLock);
}
WriteConnection = SQLite3.Open( protected SQLiteDatabaseConnection CreateWriteConnection()
{
var writeConnection = SQLite3.Open(
DbFilePath, DbFilePath,
DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite, DefaultConnectionFlags | ConnectionFlags.Create | ConnectionFlags.ReadWrite,
null); null);
if (CacheSize.HasValue) if (CacheSize.HasValue)
{ {
WriteConnection.Execute("PRAGMA cache_size=" + CacheSize.Value); writeConnection.Execute("PRAGMA cache_size=" + CacheSize.Value);
} }
if (!string.IsNullOrWhiteSpace(LockingMode)) if (!string.IsNullOrWhiteSpace(LockingMode))
{ {
WriteConnection.Execute("PRAGMA locking_mode=" + LockingMode); writeConnection.Execute("PRAGMA locking_mode=" + LockingMode);
} }
if (!string.IsNullOrWhiteSpace(JournalMode)) if (!string.IsNullOrWhiteSpace(JournalMode))
{ {
WriteConnection.Execute("PRAGMA journal_mode=" + JournalMode); writeConnection.Execute("PRAGMA journal_mode=" + JournalMode);
} }
if (JournalSizeLimit.HasValue) if (JournalSizeLimit.HasValue)
{ {
WriteConnection.Execute("PRAGMA journal_size_limit=" + (int)JournalSizeLimit.Value); writeConnection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
} }
if (Synchronous.HasValue) if (Synchronous.HasValue)
{ {
WriteConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value); writeConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
} }
if (PageSize.HasValue) if (PageSize.HasValue)
{ {
WriteConnection.Execute("PRAGMA page_size=" + PageSize.Value); writeConnection.Execute("PRAGMA page_size=" + PageSize.Value);
} }
WriteConnection.Execute("PRAGMA temp_store=" + (int)TempStore); writeConnection.Execute("PRAGMA temp_store=" + (int)TempStore);
// Configuration and pragmas can affect VACUUM so it needs to be last. return writeConnection;
WriteConnection.Execute("VACUUM"); }
return new ManagedConnection(WriteConnection, WriteLock); protected SQLiteDatabaseConnection CreateReadConnection()
{
var connection = SQLite3.Open(
DbFilePath,
DefaultConnectionFlags | ConnectionFlags.ReadOnly,
null);
if (CacheSize.HasValue)
{
connection.Execute("PRAGMA cache_size=" + CacheSize.Value);
}
if (!string.IsNullOrWhiteSpace(LockingMode))
{
connection.Execute("PRAGMA locking_mode=" + LockingMode);
}
if (!string.IsNullOrWhiteSpace(JournalMode))
{
connection.Execute("PRAGMA journal_mode=" + JournalMode);
}
if (JournalSizeLimit.HasValue)
{
connection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
}
if (Synchronous.HasValue)
{
connection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
}
connection.Execute("PRAGMA temp_store=" + (int)TempStore);
return connection;
} }
public IStatement PrepareStatement(ManagedConnection connection, string sql) public IStatement PrepareStatement(ManagedConnection connection, string sql)
@ -166,18 +218,6 @@ namespace Emby.Server.Implementations.Data
public IStatement PrepareStatement(IDatabaseConnection connection, string sql) public IStatement PrepareStatement(IDatabaseConnection connection, string sql)
=> connection.PrepareStatement(sql); => connection.PrepareStatement(sql);
public IStatement[] PrepareAll(IDatabaseConnection connection, IReadOnlyList<string> sql)
{
int len = sql.Count;
IStatement[] statements = new IStatement[len];
for (int i = 0; i < len; i++)
{
statements[i] = connection.PrepareStatement(sql[i]);
}
return statements;
}
protected bool TableExists(ManagedConnection connection, string name) protected bool TableExists(ManagedConnection connection, string name)
{ {
return connection.RunInTransaction( return connection.RunInTransaction(
@ -252,22 +292,10 @@ namespace Emby.Server.Implementations.Data
if (dispose) if (dispose)
{ {
WriteLock.Wait(); WriteConnections.Dispose();
try ReadConnections.Dispose();
{
WriteConnection?.Dispose();
}
finally
{
WriteLock.Release();
}
WriteLock.Dispose();
} }
WriteConnection = null;
WriteLock = null;
_disposed = true; _disposed = true;
} }
} }

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Concurrent;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data;
/// <summary>
/// A pool of SQLite Database connections.
/// </summary>
public sealed class ConnectionPool : IDisposable
{
private readonly BlockingCollection<SQLiteDatabaseConnection> _connections = new();
private bool _disposed;
/// <summary>
/// Initializes a new instance of the <see cref="ConnectionPool" /> class.
/// </summary>
/// <param name="count">The number of database connection to create.</param>
/// <param name="factory">Factory function to create the database connections.</param>
public ConnectionPool(int count, Func<SQLiteDatabaseConnection> factory)
{
for (int i = 0; i < count; i++)
{
_connections.Add(factory.Invoke());
}
}
/// <summary>
/// Gets a database connection from the pool if one is available, otherwise blocks.
/// </summary>
/// <returns>A database connection.</returns>
public ManagedConnection GetConnection()
{
if (_disposed)
{
ThrowObjectDisposedException();
}
return new ManagedConnection(_connections.Take(), this);
static void ThrowObjectDisposedException()
{
throw new ObjectDisposedException(nameof(ConnectionPool));
}
}
/// <summary>
/// Return a database connection to the pool.
/// </summary>
/// <param name="connection">The database connection to return.</param>
public void Return(SQLiteDatabaseConnection connection)
{
if (_disposed)
{
connection.Dispose();
return;
}
_connections.Add(connection);
}
/// <inheritdoc />
public void Dispose()
{
if (_disposed)
{
return;
}
foreach (var connection in _connections)
{
connection.Dispose();
}
_connections.Dispose();
_disposed = true;
}
}

View File

@ -2,23 +2,22 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using SQLitePCL.pretty; using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data namespace Emby.Server.Implementations.Data
{ {
public sealed class ManagedConnection : IDisposable public sealed class ManagedConnection : IDisposable
{ {
private readonly SemaphoreSlim _writeLock; private readonly ConnectionPool _pool;
private SQLiteDatabaseConnection? _db; private SQLiteDatabaseConnection _db;
private bool _disposed = false; private bool _disposed = false;
public ManagedConnection(SQLiteDatabaseConnection db, SemaphoreSlim writeLock) public ManagedConnection(SQLiteDatabaseConnection db, ConnectionPool pool)
{ {
_db = db; _db = db;
_writeLock = writeLock; _pool = pool;
} }
public IStatement PrepareStatement(string sql) public IStatement PrepareStatement(string sql)
@ -73,9 +72,9 @@ namespace Emby.Server.Implementations.Data
return; return;
} }
_writeLock.Release(); _pool.Return(_db);
_db = null; // Don't dispose it _db = null!; // Don't dispose it
_disposed = true; _disposed = true;
} }
} }

View File

@ -336,6 +336,7 @@ namespace Emby.Server.Implementations.Data
_jsonOptions = JsonDefaults.Options; _jsonOptions = JsonDefaults.Options;
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
ReadConnectionsCount = Environment.ProcessorCount * 2;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -347,10 +348,10 @@ namespace Emby.Server.Implementations.Data
/// <summary> /// <summary>
/// Opens the connection to the database. /// Opens the connection to the database.
/// </summary> /// </summary>
/// <param name="userDataRepo">The user data repository.</param> public override void Initialize()
/// <param name="userManager">The user manager.</param>
public void Initialize(SqliteUserDataRepository userDataRepo, IUserManager userManager)
{ {
base.Initialize();
const string CreateMediaStreamsTableCommand const string CreateMediaStreamsTableCommand
= "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, IsHearingImpaired BIT NULL, PRIMARY KEY (ItemId, StreamIndex))"; = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, CodecTag TEXT NULL, Comment TEXT NULL, NalLengthSize TEXT NULL, IsAvc BIT NULL, Title TEXT NULL, TimeBase TEXT NULL, CodecTimeBase TEXT NULL, ColorPrimaries TEXT NULL, ColorSpace TEXT NULL, ColorTransfer TEXT NULL, DvVersionMajor INT NULL, DvVersionMinor INT NULL, DvProfile INT NULL, DvLevel INT NULL, RpuPresentFlag INT NULL, ElPresentFlag INT NULL, BlPresentFlag INT NULL, DvBlSignalCompatibilityId INT NULL, IsHearingImpaired BIT NULL, PRIMARY KEY (ItemId, StreamIndex))";
@ -551,8 +552,6 @@ namespace Emby.Server.Implementations.Data
connection.RunQueries(postQueries); connection.RunQueries(postQueries);
} }
userDataRepo.Initialize(userManager, WriteLock, WriteConnection);
} }
public void SaveImages(BaseItem item) public void SaveImages(BaseItem item)
@ -624,14 +623,8 @@ namespace Emby.Server.Implementations.Data
private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples) private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
{ {
var statements = PrepareAll(db, new string[] using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
{ using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
SaveItemCommandText,
"delete from AncestorIds where ItemId=@ItemId"
});
using (var saveItemStatement = statements[0])
using (var deleteAncestorsStatement = statements[1])
{ {
var requiresReset = false; var requiresReset = false;
foreach (var tuple in tuples) foreach (var tuple in tuples)
@ -1286,15 +1279,13 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery))
{ {
using (var statement = PrepareStatement(connection, _retrieveItemColumnsSelectQuery)) statement.TryBind("@guid", id);
{
statement.TryBind("@guid", id);
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
return GetItem(row, new InternalItemsQuery()); return GetItem(row, new InternalItemsQuery());
}
} }
} }
@ -1309,7 +1300,8 @@ namespace Emby.Server.Implementations.Data
{ {
return false; return false;
} }
else if (type == typeof(UserRootFolder))
if (type == typeof(UserRootFolder))
{ {
return false; return false;
} }
@ -1319,55 +1311,68 @@ namespace Emby.Server.Implementations.Data
{ {
return false; return false;
} }
else if (type == typeof(MusicArtist))
if (type == typeof(MusicArtist))
{ {
return false; return false;
} }
else if (type == typeof(Person))
if (type == typeof(Person))
{ {
return false; return false;
} }
else if (type == typeof(MusicGenre))
if (type == typeof(MusicGenre))
{ {
return false; return false;
} }
else if (type == typeof(Genre))
if (type == typeof(Genre))
{ {
return false; return false;
} }
else if (type == typeof(Studio))
if (type == typeof(Studio))
{ {
return false; return false;
} }
else if (type == typeof(PlaylistsFolder))
if (type == typeof(PlaylistsFolder))
{ {
return false; return false;
} }
else if (type == typeof(PhotoAlbum))
if (type == typeof(PhotoAlbum))
{ {
return false; return false;
} }
else if (type == typeof(Year))
if (type == typeof(Year))
{ {
return false; return false;
} }
else if (type == typeof(Book))
if (type == typeof(Book))
{ {
return false; return false;
} }
else if (type == typeof(LiveTvProgram))
if (type == typeof(LiveTvProgram))
{ {
return false; return false;
} }
else if (type == typeof(AudioBook))
if (type == typeof(AudioBook))
{ {
return false; return false;
} }
else if (type == typeof(Audio))
if (type == typeof(Audio))
{ {
return false; return false;
} }
else if (type == typeof(MusicAlbum))
if (type == typeof(MusicAlbum))
{ {
return false; return false;
} }
@ -1958,22 +1963,19 @@ namespace Emby.Server.Implementations.Data
{ {
CheckDisposed(); CheckDisposed();
var chapters = new List<ChapterInfo>();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
{ {
var chapters = new List<ChapterInfo>(); statement.TryBind("@ItemId", item.Id);
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) foreach (var row in statement.ExecuteQuery())
{ {
statement.TryBind("@ItemId", item.Id); chapters.Add(GetChapter(row, item));
foreach (var row in statement.ExecuteQuery())
{
chapters.Add(GetChapter(row, item));
}
} }
return chapters;
} }
return chapters;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -1982,16 +1984,14 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex"))
{ {
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId and ChapterIndex=@ChapterIndex")) statement.TryBind("@ItemId", item.Id);
{ statement.TryBind("@ChapterIndex", index);
statement.TryBind("@ItemId", item.Id);
statement.TryBind("@ChapterIndex", index);
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
return GetChapter(row, item); return GetChapter(row, item);
}
} }
} }
@ -2378,7 +2378,7 @@ namespace Emby.Server.Implementations.Data
else else
{ {
builder.Append( builder.Append(
@"(SELECT CASE WHEN InheritedParentalRatingValue=0 @"(SELECT CASE WHEN COALESCE(InheritedParentalRatingValue, 0)=0
THEN 0 THEN 0
ELSE 10.0 / (1.0 + ABS(InheritedParentalRatingValue - @InheritedParentalRatingValue)) ELSE 10.0 / (1.0 + ABS(InheritedParentalRatingValue - @InheritedParentalRatingValue))
END)"); END)");
@ -2392,6 +2392,7 @@ namespace Emby.Server.Implementations.Data
// genres, tags, studios, person, year? // genres, tags, studios, person, year?
builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))"); builder.Append("+ (Select count(1) * 10 from ItemValues where ItemId=Guid and CleanValue in (select CleanValue from ItemValues where ItemId=@SimilarItemId))");
builder.Append("+ (Select count(1) * 10 from People where ItemId=Guid and Name in (select Name from People where ItemId=@SimilarItemId))");
if (item is MusicArtist) if (item is MusicArtist)
{ {
@ -2843,13 +2844,10 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
{ {
var itemQueryStatement = PrepareStatement(db, itemQuery);
var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery);
if (!isReturningZeroItems) if (!isReturningZeroItems)
{ {
using (new QueryTimeLogger(Logger, itemQuery, "GetItems.ItemQuery")) using (new QueryTimeLogger(Logger, itemQuery, "GetItems.ItemQuery"))
using (var statement = itemQueryStatement) using (var statement = PrepareStatement(db, itemQuery))
{ {
if (EnableJoinUserData(query)) if (EnableJoinUserData(query))
{ {
@ -2884,7 +2882,7 @@ namespace Emby.Server.Implementations.Data
if (query.EnableTotalRecordCount) if (query.EnableTotalRecordCount)
{ {
using (new QueryTimeLogger(Logger, totalRecordCountQuery, "GetItems.TotalRecordCount")) using (new QueryTimeLogger(Logger, totalRecordCountQuery, "GetItems.TotalRecordCount"))
using (var statement = totalRecordCountQueryStatement) using (var statement = PrepareStatement(db, totalRecordCountQuery))
{ {
if (EnableJoinUserData(query)) if (EnableJoinUserData(query))
{ {
@ -4753,22 +4751,20 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText.Append(" LIMIT ").Append(query.Limit); commandText.Append(" LIMIT ").Append(query.Limit);
} }
var list = new List<string>();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText.ToString()))
{ {
var list = new List<string>(); // Run this again to bind the params
using (var statement = PrepareStatement(connection, commandText.ToString())) GetPeopleWhereClauses(query, statement);
foreach (var row in statement.ExecuteQuery())
{ {
// Run this again to bind the params list.Add(row.GetString(0));
GetPeopleWhereClauses(query, statement);
foreach (var row in statement.ExecuteQuery())
{
list.Add(row.GetString(0));
}
} }
return list;
} }
return list;
} }
public List<PersonInfo> GetPeople(InternalPeopleQuery query) public List<PersonInfo> GetPeople(InternalPeopleQuery query)
@ -4793,23 +4789,20 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
commandText += " LIMIT " + query.Limit; commandText += " LIMIT " + query.Limit;
} }
var list = new List<PersonInfo>();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText))
{ {
var list = new List<PersonInfo>(); // Run this again to bind the params
GetPeopleWhereClauses(query, statement);
using (var statement = PrepareStatement(connection, commandText)) foreach (var row in statement.ExecuteQuery())
{ {
// Run this again to bind the params list.Add(GetPerson(row));
GetPeopleWhereClauses(query, statement);
foreach (var row in statement.ExecuteQuery())
{
list.Add(GetPerson(row));
}
} }
return list;
} }
return list;
} }
private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement) private List<string> GetPeopleWhereClauses(InternalPeopleQuery query, IStatement statement)
@ -5540,7 +5533,7 @@ AND Type = @InternalPersonType)");
statement.TryBind("@Name" + index, person.Name); statement.TryBind("@Name" + index, person.Name);
statement.TryBind("@Role" + index, person.Role); statement.TryBind("@Role" + index, person.Role);
statement.TryBind("@PersonType" + index, person.Type); statement.TryBind("@PersonType" + index, person.Type.ToString());
statement.TryBind("@SortOrder" + index, person.SortOrder); statement.TryBind("@SortOrder" + index, person.SortOrder);
statement.TryBind("@ListOrder" + index, listIndex); statement.TryBind("@ListOrder" + index, listIndex);
@ -5569,9 +5562,10 @@ AND Type = @InternalPersonType)");
item.Role = role; item.Role = role;
} }
if (reader.TryGetString(3, out var type)) if (reader.TryGetString(3, out var type)
&& Enum.TryParse(type, true, out PersonKind personKind))
{ {
item.Type = type; item.Type = personKind;
} }
if (reader.TryGetInt32(4, out var sortOrder)) if (reader.TryGetInt32(4, out var sortOrder))

View File

@ -7,7 +7,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
@ -18,33 +18,32 @@ namespace Emby.Server.Implementations.Data
{ {
public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository public class SqliteUserDataRepository : BaseSqliteRepository, IUserDataRepository
{ {
private readonly IUserManager _userManager;
public SqliteUserDataRepository( public SqliteUserDataRepository(
ILogger<SqliteUserDataRepository> logger, ILogger<SqliteUserDataRepository> logger,
IApplicationPaths appPaths) IServerConfigurationManager config,
IUserManager userManager)
: base(logger) : base(logger)
{ {
DbFilePath = Path.Combine(appPaths.DataPath, "library.db"); _userManager = userManager;
DbFilePath = Path.Combine(config.ApplicationPaths.DataPath, "library.db");
} }
/// <summary> /// <summary>
/// Opens the connection to the database. /// Opens the connection to the database.
/// </summary> /// </summary>
/// <param name="userManager">The user manager.</param> public override void Initialize()
/// <param name="dbLock">The lock to use for database IO.</param>
/// <param name="dbConnection">The connection to use for database IO.</param>
public void Initialize(IUserManager userManager, SemaphoreSlim dbLock, SQLiteDatabaseConnection dbConnection)
{ {
WriteLock.Dispose(); base.Initialize();
WriteLock = dbLock;
WriteConnection?.Dispose();
WriteConnection = dbConnection;
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
var userDatasTableExists = TableExists(connection, "UserDatas"); var userDatasTableExists = TableExists(connection, "UserDatas");
var userDataTableExists = TableExists(connection, "userdata"); var userDataTableExists = TableExists(connection, "userdata");
var users = userDatasTableExists ? null : userManager.Users; var users = userDatasTableExists ? null : _userManager.Users;
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
@ -371,20 +370,5 @@ namespace Emby.Server.Implementations.Data
return userData; return userData;
} }
#pragma warning disable CA2215
/// <inheritdoc/>
/// <remarks>
/// There is nothing to dispose here since <see cref="BaseSqliteRepository.WriteLock"/> and
/// <see cref="BaseSqliteRepository.WriteConnection"/> are managed by <see cref="SqliteItemRepository"/>.
/// See <see cref="Initialize(IUserManager, SemaphoreSlim, SQLiteDatabaseConnection)"/>.
/// </remarks>
protected override void Dispose(bool dispose)
{
// The write lock and connection for the item repository are shared with the user data repository
// since they point to the same database. The item repo has responsibility for disposing these two objects,
// so the user data repo should not attempt to dispose them as well
}
#pragma warning restore CA2215
} }
} }

View File

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -523,32 +522,32 @@ namespace Emby.Server.Implementations.Dto
var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue) var people = _libraryManager.GetPeople(item).OrderBy(i => i.SortOrder ?? int.MaxValue)
.ThenBy(i => .ThenBy(i =>
{ {
if (i.IsType(PersonType.Actor)) if (i.IsType(PersonKind.Actor))
{ {
return 0; return 0;
} }
if (i.IsType(PersonType.GuestStar)) if (i.IsType(PersonKind.GuestStar))
{ {
return 1; return 1;
} }
if (i.IsType(PersonType.Director)) if (i.IsType(PersonKind.Director))
{ {
return 2; return 2;
} }
if (i.IsType(PersonType.Writer)) if (i.IsType(PersonKind.Writer))
{ {
return 3; return 3;
} }
if (i.IsType(PersonType.Producer)) if (i.IsType(PersonKind.Producer))
{ {
return 4; return 4;
} }
if (i.IsType(PersonType.Composer)) if (i.IsType(PersonKind.Composer))
{ {
return 4; return 4;
} }
@ -572,9 +571,7 @@ namespace Emby.Server.Implementations.Dto
return null; return null;
} }
}).Where(i => i is not null) }).Where(i => i is not null)
.Where(i => user is null ? .Where(i => user is null || i.IsVisible(user))
true :
i.IsVisible(user))
.DistinctBy(x => x.Name, StringComparer.OrdinalIgnoreCase) .DistinctBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase);

View File

@ -1503,6 +1503,12 @@ namespace Emby.Server.Implementations.Library
}); });
query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray(); query.TopParentIds = userViews.SelectMany(i => GetTopParentIdsForQuery(i, user)).ToArray();
// Prevent searching in all libraries due to empty filter
if (query.TopParentIds.Length == 0)
{
query.TopParentIds = new[] { Guid.NewGuid() };
}
} }
} }
@ -1879,7 +1885,7 @@ namespace Emby.Server.Implementations.Library
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path); _logger.LogError(ex, "Cannot get image dimensions for {ImagePath}", image.Path);
size = new ImageDimensions(0, 0); size = default;
image.Width = 0; image.Width = 0;
image.Height = 0; image.Height = 0;
} }
@ -2743,9 +2749,7 @@ namespace Emby.Server.Implementations.Library
} }
}) })
.Where(i => i is not null) .Where(i => i is not null)
.Where(i => query.User is null ? .Where(i => query.User is null || i.IsVisible(query.User))
true :
i.IsVisible(query.User))
.ToList(); .ToList();
} }

View File

@ -154,8 +154,8 @@ namespace Emby.Server.Implementations.Library
// If file is strm or main media stream is missing, force a metadata refresh with remote probing // If file is strm or main media stream is missing, force a metadata refresh with remote probing
if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder if (allowMediaProbe && mediaSources[0].Type != MediaSourceType.Placeholder
&& (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase) && (item.Path.EndsWith(".strm", StringComparison.OrdinalIgnoreCase)
|| (item.MediaType == MediaType.Video && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Video)) || (item.MediaType == MediaType.Video && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Video))
|| (item.MediaType == MediaType.Audio && !mediaSources[0].MediaStreams.Any(i => i.Type == MediaStreamType.Audio)))) || (item.MediaType == MediaType.Audio && mediaSources[0].MediaStreams.All(i => i.Type != MediaStreamType.Audio))))
{ {
await item.RefreshMetadata( await item.RefreshMetadata(
new MetadataRefreshOptions(_directoryService) new MetadataRefreshOptions(_directoryService)

View File

@ -78,7 +78,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
Set3DFormat(videoTmp); Set3DFormat(videoTmp);
return videoTmp; return videoTmp;
} }
else if (IsBluRayDirectory(filename))
if (IsBluRayDirectory(filename))
{ {
var videoTmp = new TVideoType var videoTmp = new TVideoType
{ {

View File

@ -4,6 +4,7 @@ using System.IO;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.Video; using Emby.Naming.Video;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <summary> /// <summary>
/// Resolves a Path into a Video or Video subclass. /// Resolves a Path into a Video or Video subclass.
/// </summary> /// </summary>
internal class ExtraResolver internal class ExtraResolver : BaseVideoResolver<Video>
{ {
private readonly NamingOptions _namingOptions; private readonly NamingOptions _namingOptions;
private readonly IItemResolver[] _trailerResolvers; private readonly IItemResolver[] _trailerResolvers;
@ -28,10 +29,16 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param> /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService) public ExtraResolver(ILogger<ExtraResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService)
: base(logger, namingOptions, directoryService)
{ {
_namingOptions = namingOptions; _namingOptions = namingOptions;
_trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions, directoryService) }; _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(logger, namingOptions, directoryService) };
_videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(logger, namingOptions, directoryService) }; _videoResolvers = new IItemResolver[] { this };
}
protected override Video Resolve(ItemResolveArgs args)
{
return ResolveVideo<Video>(args, true);
} }
/// <summary> /// <summary>

View File

@ -627,10 +627,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_timerProvider.Update(existingTimer); _timerProvider.Update(existingTimer);
return Task.FromResult(existingTimer.Id); return Task.FromResult(existingTimer.Id);
} }
else
{ throw new ArgumentException("A scheduled recording already exists for this program.");
throw new ArgumentException("A scheduled recording already exists for this program.");
}
} }
info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture); info.Id = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
@ -1866,8 +1864,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false); await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false); await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
string id; if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out var id))
if (timer.SeriesProviderIds.TryGetValue(MetadataProvider.Tvdb.ToString(), out id))
{ {
await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false); await writer.WriteElementStringAsync(null, "id", null, id).ConfigureAwait(false);
} }
@ -2032,7 +2029,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var people = item.Id.Equals(default) ? new List<PersonInfo>() : _libraryManager.GetPeople(item); var people = item.Id.Equals(default) ? new List<PersonInfo>() : _libraryManager.GetPeople(item);
var directors = people var directors = people
.Where(i => IsPersonType(i, PersonType.Director)) .Where(i => i.IsType(PersonKind.Director))
.Select(i => i.Name) .Select(i => i.Name)
.ToList(); .ToList();
@ -2042,7 +2039,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
var writers = people var writers = people
.Where(i => IsPersonType(i, PersonType.Writer)) .Where(i => i.IsType(PersonKind.Writer))
.Select(i => i.Name) .Select(i => i.Name)
.Distinct(StringComparer.OrdinalIgnoreCase) .Distinct(StringComparer.OrdinalIgnoreCase)
.ToList(); .ToList();
@ -2122,10 +2119,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
} }
} }
private static bool IsPersonType(PersonInfo person, string type)
=> string.Equals(person.Type, type, StringComparison.OrdinalIgnoreCase)
|| string.Equals(person.Role, type, StringComparison.OrdinalIgnoreCase);
private LiveTvProgram GetProgramInfoFromCache(string programId) private LiveTvProgram GetProgramInfoFromCache(string programId)
{ {
var query = new InternalItemsQuery var query = new InternalItemsQuery

View File

@ -415,14 +415,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{ {
return null; return null;
} }
else if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
{ {
return uri; return uri;
} }
else
{ return apiUrl + "/image/" + uri + "?token=" + token;
return apiUrl + "/image/" + uri + "?token=" + token;
}
} }
private static double GetAspectRatio(ImageDataDto i) private static double GetAspectRatio(ImageDataDto i)

View File

@ -51,7 +51,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public async Task<bool> CheckTunerAvailability(IPAddress remoteIP, int tuner, CancellationToken cancellationToken) public async Task<bool> CheckTunerAvailability(IPAddress remoteIP, int tuner, CancellationToken cancellationToken)
{ {
using var client = new TcpClient(); using var client = new TcpClient();
await client.ConnectAsync(remoteIP, HdHomeRunPort).ConfigureAwait(false); await client.ConnectAsync(remoteIP, HdHomeRunPort, cancellationToken).ConfigureAwait(false);
using var stream = client.GetStream(); using var stream = client.GetStream();
return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false); return await CheckTunerAvailability(stream, tuner, cancellationToken).ConfigureAwait(false);

View File

@ -170,9 +170,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].AsSpan().Trim() : ReadOnlySpan<char>.Empty; var nameInExtInf = nameParts.Length > 1 ? nameParts[^1].AsSpan().Trim() : ReadOnlySpan<char>.Empty;
string numberString = null; string numberString = null;
string attributeValue;
if (attributes.TryGetValue("tvg-chno", out attributeValue) if (attributes.TryGetValue("tvg-chno", out var attributeValue)
&& double.TryParse(attributeValue, CultureInfo.InvariantCulture, out _)) && double.TryParse(attributeValue, CultureInfo.InvariantCulture, out _))
{ {
numberString = attributeValue; numberString = attributeValue;

View File

@ -1,27 +1,27 @@
{ {
"DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
"DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
"Collections": "সংগ্রহ", "Collections": "সংগ্রহশালা",
"ChapterNameValue": "অধ্যায় {0}", "ChapterNameValue": "অধ্যায় {0}",
"Channels": "চ্যানেল", "Channels": "চ্যানেলসমূহ",
"CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
"Books": "বই", "Books": "পুস্তকসমূহ",
"AuthenticationSucceededWithUserName": "{0} অনুমোদন সফল", "AuthenticationSucceededWithUserName": "{0} অনুমোদন সফল",
"Artists": "শিল্পীরা", "Artists": "শিল্পীগণ",
"Application": "অ্যাপ্লিকেশন", "Application": "অ্যাপ্লিকেশন",
"Albums": "অ্যালবামগুলো", "Albums": "অ্যালবামসমূহ",
"HeaderFavoriteEpisodes": "প্রিব পর্বগুলো", "HeaderFavoriteEpisodes": "প্রিব পর্বগুলো",
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা", "HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো", "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন", "HeaderContinueWatching": "দেখতে থাকুন",
"HeaderAlbumArtists": "লবাম শিল্পীবৃন্দ", "HeaderAlbumArtists": "অ্যালবাম শিল্পীবৃন্দ",
"Genres": "শৈলী", "Genres": "শৈলীধারাসমূহ",
"Folders": "ফোল্ডারগুলো", "Folders": "ফোল্ডারসমূহ",
"Favorites": "পছন্দসমূহ", "Favorites": "পছন্দসমূহ",
"FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে", "FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
"AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {0}", "AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {0}",
"VersionNumber": "সংস্করণ {0}", "VersionNumber": "সংস্করণ {0}",
"ValueSpecialEpisodeName": "বিশেষ - {0}", "ValueSpecialEpisodeName": "বিশেষ পর্ব - {0}",
"ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে", "ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
"UserStoppedPlayingItemWithValues": "{2}তে {1} বাজানো শেষ করেছেন {0}", "UserStoppedPlayingItemWithValues": "{2}তে {1} বাজানো শেষ করেছেন {0}",
"UserStartedPlayingItemWithValues": "{2}তে {1} বাজাচ্ছেন {0}", "UserStartedPlayingItemWithValues": "{2}তে {1} বাজাচ্ছেন {0}",
@ -36,10 +36,10 @@
"User": "ব্যবহারকারী", "User": "ব্যবহারকারী",
"TvShows": "টিভি শোগুলো", "TvShows": "টিভি শোগুলো",
"System": "সিস্টেম", "System": "সিস্টেম",
"Sync": "সিংক", "Sync": "সমলয় স্থাপন",
"SubtitleDownloadFailureFromForItem": "{2} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ", "SubtitleDownloadFailureFromForItem": "{2} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ",
"StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।", "StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।",
"Songs": "গানগুলো", "Songs": "সঙ্গীতসমূহ",
"Shows": "টিভি পর্ব", "Shows": "টিভি পর্ব",
"ServerNameNeedsToBeRestarted": "{0} রিস্টার্ট করা প্রয়োজন", "ServerNameNeedsToBeRestarted": "{0} রিস্টার্ট করা প্রয়োজন",
"ScheduledTaskStartedWithName": "{0} শুরু হয়েছে", "ScheduledTaskStartedWithName": "{0} শুরু হয়েছে",
@ -49,8 +49,8 @@
"PluginUninstalledWithName": "{0} বাদ দেয়া হয়েছে", "PluginUninstalledWithName": "{0} বাদ দেয়া হয়েছে",
"PluginInstalledWithName": "{0} ইন্সটল করা হয়েছে", "PluginInstalledWithName": "{0} ইন্সটল করা হয়েছে",
"Plugin": "প্লাগিন", "Plugin": "প্লাগিন",
"Playlists": "প্লেলিস্ট", "Playlists": "প্লে লিস্ট সমূহ",
"Photos": "ছবিগুলো", "Photos": "চিত্রসমূহ",
"NotificationOptionVideoPlaybackStopped": "ভিডিও চলা বন্ধ", "NotificationOptionVideoPlaybackStopped": "ভিডিও চলা বন্ধ",
"NotificationOptionVideoPlayback": "ভিডিও চলা শুরু হয়েছে", "NotificationOptionVideoPlayback": "ভিডিও চলা শুরু হয়েছে",
"NotificationOptionUserLockedOut": "ব্যবহারকারী ঢুকতে পারছে না", "NotificationOptionUserLockedOut": "ব্যবহারকারী ঢুকতে পারছে না",
@ -71,9 +71,9 @@
"NameSeasonUnknown": "সিজন অজানা", "NameSeasonUnknown": "সিজন অজানা",
"NameSeasonNumber": "সিজন {0}", "NameSeasonNumber": "সিজন {0}",
"NameInstallFailed": "{0} ইন্সটল ব্যর্থ", "NameInstallFailed": "{0} ইন্সটল ব্যর্থ",
"MusicVideos": "গানের ভিডিও", "MusicVideos": "সঙ্গীত ভিডিয়ো সমূহ",
"Music": "গান", "Music": "গান",
"Movies": "চলচ্চিত্র", "Movies": "চলচ্চিত্রসমূহ",
"MixedContent": "মিশ্র কন্টেন্ট", "MixedContent": "মিশ্র কন্টেন্ট",
"MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে", "MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে",
"HeaderRecordingGroups": "রেকর্ডিং দল", "HeaderRecordingGroups": "রেকর্ডিং দল",
@ -117,5 +117,11 @@
"Forced": "জোরকরে", "Forced": "জোরকরে",
"TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.", "TaskCleanActivityLogDescription": "নির্ধারিত সময়ের আগের কাজের হিসাব মুছে দিন খালি করুন.",
"TaskCleanActivityLog": "কাজের ফাইল খালি করুন", "TaskCleanActivityLog": "কাজের ফাইল খালি করুন",
"Default": "প্রাথমিক" "Default": "প্রাথমিক",
"HearingImpaired": "দুর্বল শ্রবণক্ষমতাধরদের জন্য",
"TaskOptimizeDatabaseDescription": "তথ্যভাণ্ডার সুবিন্যস্ত করে ও অব্যবহৃত জায়গা ছেড়ে দেয়। লাইব্রেরী স্ক্যান অথবা যেকোনো তথ্যভাণ্ডার পরিবর্তনের পর এই প্রক্রিয়া চালালে তথ্যভাণ্ডারের তথ্য প্রদান দ্রুততর হতে পারে।",
"External": "বাহ্যিক",
"TaskOptimizeDatabase": "তথ্যভাণ্ডার সুবিন্যাস",
"TaskKeyframeExtractor": "কি-ফ্রেম নিষ্কাশক",
"TaskKeyframeExtractorDescription": "ভিডিয়ো থেকে কি-ফ্রেম নিষ্কাশনের মাধ্যমে অধিকতর সঠিক HLS প্লে লিস্ট তৈরী করে। এই প্রক্রিয়া দীর্ঘ সময় ধরে চলতে পারে।"
} }

View File

@ -5,7 +5,7 @@
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres", "Books": "Llibres",
"CameraImageUploadedFrom": "S'ha pujat una nova imatge des de la camera desde {0}", "CameraImageUploadedFrom": "S'ha pujat una nova imatge de càmera des de {0}",
"Channels": "Canals", "Channels": "Canals",
"ChapterNameValue": "Capítol {0}", "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions", "Collections": "Col·leccions",
@ -16,65 +16,65 @@
"Folders": "Carpetes", "Folders": "Carpetes",
"Genres": "Gèneres", "Genres": "Gèneres",
"HeaderAlbumArtists": "Artistes de l'àlbum", "HeaderAlbumArtists": "Artistes de l'àlbum",
"HeaderContinueWatching": "Continua Veient", "HeaderContinueWatching": "Continuar veient",
"HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteAlbums": "Àlbums preferits",
"HeaderFavoriteArtists": "Artistes Predilectes", "HeaderFavoriteArtists": "Artistes preferits",
"HeaderFavoriteEpisodes": "Episodis Predilectes", "HeaderFavoriteEpisodes": "Episodis preferits",
"HeaderFavoriteShows": "Sèries Predilectes", "HeaderFavoriteShows": "Sèries preferides",
"HeaderFavoriteSongs": "Cançons Predilectes", "HeaderFavoriteSongs": "Cançons preferides",
"HeaderLiveTV": "TV en Directe", "HeaderLiveTV": "TV en directe",
"HeaderNextUp": "A continuació", "HeaderNextUp": "A continuació",
"HeaderRecordingGroups": "Grups d'Enregistrament", "HeaderRecordingGroups": "Grups d'enregistrament",
"HomeVideos": "Vídeos Domèstics", "HomeVideos": "Vídeos domèstics",
"Inherit": "Hereta", "Inherit": "Hereta",
"ItemAddedWithName": "{0} ha estat afegit a la biblioteca", "ItemAddedWithName": "{0} ha sigut afegit a la biblioteca",
"ItemRemovedWithName": "{0} ha estat eliminat de la biblioteca", "ItemRemovedWithName": "{0} ha sigut eliminat de la biblioteca",
"LabelIpAddressValue": "Adreça IP: {0}", "LabelIpAddressValue": "Adreça IP: {0}",
"LabelRunningTimeValue": "Temps en funcionament: {0}", "LabelRunningTimeValue": "Temps en funcionament: {0}",
"Latest": "Darreres", "Latest": "Darrers",
"MessageApplicationUpdated": "El Servidor de Jellyfin ha estat actualitzat", "MessageApplicationUpdated": "El servidor de Jellyfin ha estat actualitzat",
"MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}", "MessageApplicationUpdatedTo": "El servidor de Jellyfin ha estat actualitzat a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada", "MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
"MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor", "MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
"MixedContent": "Contingut barrejat", "MixedContent": "Contingut barrejat",
"Movies": "Pel·lícules", "Movies": "Pel·lícules",
"Music": "Música", "Music": "Música",
"MusicVideos": "Vídeos Musicals", "MusicVideos": "Videoclips",
"NameInstallFailed": "{0} instal·lació fallida", "NameInstallFailed": "{0} instal·lació fallida",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NameSeasonUnknown": "Temporada Desconeguda", "NameSeasonUnknown": "Temporada desconeguda",
"NewVersionIsAvailable": "Una nova versió del Servidor Jellyfin està disponible per descarregar.", "NewVersionIsAvailable": "Una nova versió del servidor de Jellyfin està disponible per a descarregar.",
"NotificationOptionApplicationUpdateAvailable": "Actualització d'aplicació disponible", "NotificationOptionApplicationUpdateAvailable": "Actualització de l'aplicació disponible",
"NotificationOptionApplicationUpdateInstalled": "Actualització d'aplicació instal·lada", "NotificationOptionApplicationUpdateInstalled": "Actualització de l'aplicació instal·lada",
"NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada", "NotificationOptionAudioPlayback": "Reproducció d'àudio iniciada",
"NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada", "NotificationOptionAudioPlaybackStopped": "Reproducció d'àudio aturada",
"NotificationOptionCameraImageUploaded": "Imatge de càmera pujada", "NotificationOptionCameraImageUploaded": "Imatge de càmera pujada",
"NotificationOptionInstallationFailed": "Instal·lació fallida", "NotificationOptionInstallationFailed": "Instal·lació fallida",
"NotificationOptionNewLibraryContent": "Nou contingut afegit", "NotificationOptionNewLibraryContent": "Nou contingut afegit",
"NotificationOptionPluginError": "Un connector ha fallat", "NotificationOptionPluginError": "Un complement ha fallat",
"NotificationOptionPluginInstalled": "Connector instal·lat", "NotificationOptionPluginInstalled": "Complement instal·lat",
"NotificationOptionPluginUninstalled": "Connector desinstal·lat", "NotificationOptionPluginUninstalled": "Complement desinstal·lat",
"NotificationOptionPluginUpdateInstalled": "Actualització de connector instal·lada", "NotificationOptionPluginUpdateInstalled": "Actualització de complement instal·lada",
"NotificationOptionServerRestartRequired": "Reinici del servidor requerit", "NotificationOptionServerRestartRequired": "Reinici del servidor requerit",
"NotificationOptionTaskFailed": "Tasca programada fallida", "NotificationOptionTaskFailed": "Tasca programada fallida",
"NotificationOptionUserLockedOut": "Usuari tancat", "NotificationOptionUserLockedOut": "Usuari expulsat",
"NotificationOptionVideoPlayback": "Reproducció de video iniciada", "NotificationOptionVideoPlayback": "Reproducció de vídeo iniciada",
"NotificationOptionVideoPlaybackStopped": "Reproducció de video aturada", "NotificationOptionVideoPlaybackStopped": "Reproducció de vídeo aturada",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Llistes de reproducció", "Playlists": "Llistes de reproducció",
"Plugin": "Connector", "Plugin": "Complement",
"PluginInstalledWithName": "{0} ha estat instal·lat", "PluginInstalledWithName": "{0} ha estat instal·lat",
"PluginUninstalledWithName": "{0} ha estat desinstal·lat", "PluginUninstalledWithName": "{0} ha estat desinstal·lat",
"PluginUpdatedWithName": "{0} ha estat actualitzat", "PluginUpdatedWithName": "{0} ha estat actualitzat",
"ProviderValue": "Proveïdor: {0}", "ProviderValue": "Proveïdor: {0}",
"ScheduledTaskFailedWithName": "{0} ha fallat", "ScheduledTaskFailedWithName": "{0} ha fallat",
"ScheduledTaskStartedWithName": "{0} iniciat", "ScheduledTaskStartedWithName": "{0} s'ha iniciat",
"ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat", "ServerNameNeedsToBeRestarted": "{0} necessita ser reiniciat",
"Shows": "Sèries", "Shows": "Sèries",
"Songs": "Cançons", "Songs": "Cançons",
"StartupEmbyServerIsLoading": "El Servidor de Jellyfin està carregant. Si et plau, prova de nou ben aviat.", "StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu-ho altre cop aviat.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}", "SubtitleDownloadFailureFromForItem": "Els subtítols per a {1} no s'han pogut baixar de {0}",
"Sync": "Sincronitzar", "Sync": "Sincronitzar",
"System": "Sistema", "System": "Sistema",
"TvShows": "Sèries de TV", "TvShows": "Sèries de TV",
@ -82,11 +82,11 @@
"UserCreatedWithName": "S'ha creat l'usuari {0}", "UserCreatedWithName": "S'ha creat l'usuari {0}",
"UserDeletedWithName": "L'usuari {0} ha estat eliminat", "UserDeletedWithName": "L'usuari {0} ha estat eliminat",
"UserDownloadingItemWithValues": "{0} està descarregant {1}", "UserDownloadingItemWithValues": "{0} està descarregant {1}",
"UserLockedOutWithName": "L'usuari {0} ha sigut tancat", "UserLockedOutWithName": "L'usuari {0} ha sigut expulsat",
"UserOfflineFromDevice": "{0} s'ha desconnectat de {1}", "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
"UserOnlineFromDevice": "{0} està connectat des de {1}", "UserOnlineFromDevice": "{0} està connectat des de {1}",
"UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}", "UserPasswordChangedWithName": "La contrasenya ha estat canviada per a l'usuari {0}",
"UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per {0}", "UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per a {0}",
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}", "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1}",
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}", "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1}",
"ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva biblioteca", "ValueHasBeenAddedToLibrary": "{0} ha sigut afegit a la teva biblioteca",
@ -94,14 +94,14 @@
"VersionNumber": "Versió {0}", "VersionNumber": "Versió {0}",
"TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.", "TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.",
"TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin", "TaskDownloadMissingSubtitles": "Descarrega els subtítols que faltin",
"TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'Internet.", "TaskRefreshChannelsDescription": "Actualitza la informació dels canals d'internet.",
"TaskRefreshChannels": "Actualitza Canals", "TaskRefreshChannels": "Actualitza els canals",
"TaskCleanTranscodeDescription": "Elimina els arxius temporals de transcodificacions que tinguin més d'un dia.", "TaskCleanTranscodeDescription": "Elimina els arxius de transcodificacions que tinguin més d'un dia.",
"TaskCleanTranscode": "Neteja les transcodificacions", "TaskCleanTranscode": "Neteja les transcodificacions",
"TaskUpdatePluginsDescription": "Actualitza les extensions que estan configurades per actualitzar-se automàticament.", "TaskUpdatePluginsDescription": "Actualitza els connectors que estan configurats per a actualitzar-se automàticament.",
"TaskUpdatePlugins": "Actualitza les extensions", "TaskUpdatePlugins": "Actualitza els connectors",
"TaskRefreshPeopleDescription": "Actualitza les metadades dels actors i directors de la teva mediateca.", "TaskRefreshPeopleDescription": "Actualitza les metadades dels actors i directors de la teva mediateca.",
"TaskRefreshPeople": "Actualitza Persones", "TaskRefreshPeople": "Actualitza les persones",
"TaskCleanLogsDescription": "Esborra els logs que tinguin més de {0} dies.", "TaskCleanLogsDescription": "Esborra els logs que tinguin més de {0} dies.",
"TaskCleanLogs": "Neteja els registres", "TaskCleanLogs": "Neteja els registres",
"TaskRefreshLibraryDescription": "Escaneja la mediateca buscant fitxers nous i refresca les metadades.", "TaskRefreshLibraryDescription": "Escaneja la mediateca buscant fitxers nous i refresca les metadades.",
@ -110,12 +110,12 @@
"TaskRefreshChapterImages": "Extreure les imatges dels capítols", "TaskRefreshChapterImages": "Extreure les imatges dels capítols",
"TaskCleanCacheDescription": "Elimina els arxius temporals que ja no són necessaris per al servidor.", "TaskCleanCacheDescription": "Elimina els arxius temporals que ja no són necessaris per al servidor.",
"TaskCleanCache": "Elimina arxius temporals", "TaskCleanCache": "Elimina arxius temporals",
"TasksChannelsCategory": "Canals d'Internet", "TasksChannelsCategory": "Canals d'internet",
"TasksApplicationCategory": "Aplicació", "TasksApplicationCategory": "Aplicació",
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manteniment", "TasksMaintenanceCategory": "Manteniment",
"TaskCleanActivityLogDescription": "Eliminat entrades del registre d'activitats mes antigues que l'antiguitat configurada.", "TaskCleanActivityLogDescription": "Eliminat entrades del registre d'activitats mes antigues que l'antiguitat configurada.",
"TaskCleanActivityLog": "Buidar Registre d'Activitat", "TaskCleanActivityLog": "Buidar el registre d'activitat",
"Undefined": "Indefinit", "Undefined": "Indefinit",
"Forced": "Forçat", "Forced": "Forçat",
"Default": "Per defecte", "Default": "Per defecte",
@ -124,5 +124,5 @@
"TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.", "TaskKeyframeExtractorDescription": "Extreu fotogrames clau dels fitxers de vídeo per crear llistes de reproducció HLS més precises. Aquesta tasca pot durar molt de temps.",
"TaskKeyframeExtractor": "Extractor de fotogrames clau", "TaskKeyframeExtractor": "Extractor de fotogrames clau",
"External": "Extern", "External": "Extern",
"HearingImpaired": "Discapacitat Auditiva" "HearingImpaired": "Discapacitat auditiva"
} }

View File

@ -28,7 +28,7 @@
"NameSeasonNumber": "Tymor {0}", "NameSeasonNumber": "Tymor {0}",
"MusicVideos": "Fideos Cerddoriaeth", "MusicVideos": "Fideos Cerddoriaeth",
"MixedContent": "Cynnwys amrywiol", "MixedContent": "Cynnwys amrywiol",
"HomeVideos": "Fideos Cartref", "HomeVideos": "Genres",
"HeaderNextUp": "Nesaf i Fyny", "HeaderNextUp": "Nesaf i Fyny",
"HeaderFavoriteArtists": "Ffefryn Artistiaid", "HeaderFavoriteArtists": "Ffefryn Artistiaid",
"HeaderFavoriteAlbums": "Ffefryn Albwmau", "HeaderFavoriteAlbums": "Ffefryn Albwmau",
@ -122,5 +122,6 @@
"TaskRefreshChapterImagesDescription": "Creu mân-luniau ar gyfer fideos sydd â phenodau.", "TaskRefreshChapterImagesDescription": "Creu mân-luniau ar gyfer fideos sydd â phenodau.",
"TaskRefreshChapterImages": "Echdynnu Lluniau Pennod", "TaskRefreshChapterImages": "Echdynnu Lluniau Pennod",
"TaskCleanCacheDescription": "Dileu ffeiliau cache nad oes eu hangen ar y system mwyach.", "TaskCleanCacheDescription": "Dileu ffeiliau cache nad oes eu hangen ar y system mwyach.",
"TaskCleanCache": "Gwaghau Ffolder Cache" "TaskCleanCache": "Gwaghau Ffolder Cache",
"HearingImpaired": "Nam ar y clyw"
} }

View File

@ -1,9 +1,9 @@
{ {
"Albums": "Albummer", "Albums": "Album",
"AppDeviceValues": "App: {0}, Enhed: {1}", "AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation", "Application": "Applikation",
"Artists": "Kunstnere", "Artists": "Kunstnere",
"AuthenticationSucceededWithUserName": "{0} succesfuldt autentificeret", "AuthenticationSucceededWithUserName": "{0} er logget ind",
"Books": "Bøger", "Books": "Bøger",
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}", "CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
"Channels": "Kanaler", "Channels": "Kanaler",
@ -11,17 +11,17 @@
"Collections": "Samlinger", "Collections": "Samlinger",
"DeviceOfflineWithName": "{0} har afbrudt forbindelsen", "DeviceOfflineWithName": "{0} har afbrudt forbindelsen",
"DeviceOnlineWithName": "{0} er forbundet", "DeviceOnlineWithName": "{0} er forbundet",
"FailedLoginAttemptWithUserName": "Fejlet loginforsøg fra {0}", "FailedLoginAttemptWithUserName": "Mislykket loginforsøg fra {0}",
"Favorites": "Favoritter", "Favorites": "Favoritter",
"Folders": "Mapper", "Folders": "Mapper",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumkunstner", "HeaderAlbumArtists": "Albums kunstnere",
"HeaderContinueWatching": "Fortsæt afspilning", "HeaderContinueWatching": "Fortsæt afspilning",
"HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteAlbums": "Favorit albummer",
"HeaderFavoriteArtists": "Favoritkunstnere", "HeaderFavoriteArtists": "Favorit kunstnere",
"HeaderFavoriteEpisodes": "Favoritepisoder", "HeaderFavoriteEpisodes": "Favorit afsnit",
"HeaderFavoriteShows": "Favoritserier", "HeaderFavoriteShows": "Favorit serier",
"HeaderFavoriteSongs": "Favoritsange", "HeaderFavoriteSongs": "Favorit sange",
"HeaderLiveTV": "Live-TV", "HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Næste", "HeaderNextUp": "Næste",
"HeaderRecordingGroups": "Optagelsesgrupper", "HeaderRecordingGroups": "Optagelsesgrupper",
@ -39,90 +39,90 @@
"MixedContent": "Blandet indhold", "MixedContent": "Blandet indhold",
"Movies": "Film", "Movies": "Film",
"Music": "Musik", "Music": "Musik",
"MusicVideos": "Musik videoer", "MusicVideos": "Musikvideoer",
"NameInstallFailed": "{0} installationen mislykkedes", "NameInstallFailed": "{0} installationen mislykkedes",
"NameSeasonNumber": "Sæson {0}", "NameSeasonNumber": "Sæson {0}",
"NameSeasonUnknown": "Ukendt sæson", "NameSeasonUnknown": "Ukendt sæson",
"NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig til download.", "NewVersionIsAvailable": "En ny version af Jellyfin Server er tilgængelig.",
"NotificationOptionApplicationUpdateAvailable": "Opdatering til applikation tilgængelig", "NotificationOptionApplicationUpdateAvailable": "Opdatering til applikationen er tilgængelig",
"NotificationOptionApplicationUpdateInstalled": "Opdatering til applikation installeret", "NotificationOptionApplicationUpdateInstalled": "Opdatering til applikationen blev installeret",
"NotificationOptionAudioPlayback": "Lydafspilning påbegyndt", "NotificationOptionAudioPlayback": "Lydafspilning påbegyndt",
"NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet", "NotificationOptionAudioPlaybackStopped": "Lydafspilning stoppet",
"NotificationOptionCameraImageUploaded": "Kamerabillede uploadet", "NotificationOptionCameraImageUploaded": "Kamerabillede uploadet",
"NotificationOptionInstallationFailed": "Installationen fejlede", "NotificationOptionInstallationFailed": "Installationen mislykkedes",
"NotificationOptionNewLibraryContent": "Nyt indhold tilføjet", "NotificationOptionNewLibraryContent": "Nyt indhold tilføjet",
"NotificationOptionPluginError": "Pluginfejl", "NotificationOptionPluginError": "Plugin fejl",
"NotificationOptionPluginInstalled": "Plugin installeret", "NotificationOptionPluginInstalled": "Plugin blev installeret",
"NotificationOptionPluginUninstalled": "Plugin afinstalleret", "NotificationOptionPluginUninstalled": "Plugin blev afinstalleret",
"NotificationOptionPluginUpdateInstalled": "Opdatering til plugin installeret", "NotificationOptionPluginUpdateInstalled": "Opdatering til plugin blev installeret",
"NotificationOptionServerRestartRequired": "Genstart af server påkrævet", "NotificationOptionServerRestartRequired": "Genstart af serveren er påkrævet",
"NotificationOptionTaskFailed": "Planlagt opgave fejlet", "NotificationOptionTaskFailed": "Planlagt opgave er fejlet",
"NotificationOptionUserLockedOut": "Bruger låst ude", "NotificationOptionUserLockedOut": "Bruger er låst ude",
"NotificationOptionVideoPlayback": "Videoafspilning påbegyndt", "NotificationOptionVideoPlayback": "Videoafspilning påbegyndt",
"NotificationOptionVideoPlaybackStopped": "Videoafspilning stoppet", "NotificationOptionVideoPlaybackStopped": "Videoafspilning blev stoppet",
"Photos": "Fotoer", "Photos": "Fotos",
"Playlists": "Afspilningslister", "Playlists": "Afspilningslister",
"Plugin": "Plugin", "Plugin": "Plugin",
"PluginInstalledWithName": "{0} blev installeret", "PluginInstalledWithName": "{0} blev installeret",
"PluginUninstalledWithName": "{0} blev afinstalleret", "PluginUninstalledWithName": "{0} blev afinstalleret",
"PluginUpdatedWithName": "{0} blev opdateret", "PluginUpdatedWithName": "{0} blev opdateret",
"ProviderValue": "Udbyder: {0}", "ProviderValue": "Udbyder: {0}",
"ScheduledTaskFailedWithName": "{0} fejlet", "ScheduledTaskFailedWithName": "{0} mislykkedes",
"ScheduledTaskStartedWithName": "{0} påbegyndt", "ScheduledTaskStartedWithName": "{0} påbegyndte",
"ServerNameNeedsToBeRestarted": "{0} skal genstartes", "ServerNameNeedsToBeRestarted": "{0} skal genstartes",
"Shows": "Serier", "Shows": "Serier",
"Songs": "Sange", "Songs": "Sange",
"StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte op. Prøv venligst igen om lidt.", "StartupEmbyServerIsLoading": "Jellyfin Server er i gang med at starte. Forsøg igen om et øjeblik.",
"SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}", "SubtitleDownloadFailureForItem": "Fejlet i download af undertekster for {0}",
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke downloades fra {0} til {1}", "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke hentes fra {0} til {1}",
"Sync": "Synk", "Sync": "Synkroniser",
"System": "System", "System": "System",
"TvShows": "Tv-serier", "TvShows": "TV-serier",
"User": "Bruger", "User": "Bruger",
"UserCreatedWithName": "Bruger {0} er blevet oprettet", "UserCreatedWithName": "Bruger {0} er blevet oprettet",
"UserDeletedWithName": "Brugeren {0} er blevet slettet", "UserDeletedWithName": "Brugeren {0} er nu slettet",
"UserDownloadingItemWithValues": "{0} downloader {1}", "UserDownloadingItemWithValues": "{0} henter {1}",
"UserLockedOutWithName": "Brugeren {0} er blevet låst ude", "UserLockedOutWithName": "Brugeren {0} er blevet låst ude",
"UserOfflineFromDevice": "{0} har afbrudt fra {1}", "UserOfflineFromDevice": "{0} har afbrudt fra {1}",
"UserOnlineFromDevice": "{0} er online fra {1}", "UserOnlineFromDevice": "{0} er online fra {1}",
"UserPasswordChangedWithName": "Adgangskode er ændret for bruger {0}", "UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}",
"UserPolicyUpdatedWithName": "Brugerpolitik er blevet opdateret for {0}", "UserPolicyUpdatedWithName": "Brugerpolitikken er blevet opdateret for {0}",
"UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}", "UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}",
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}", "UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek", "ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}", "ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.", "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfigurationen.",
"TaskDownloadMissingSubtitles": "Download manglende undertekster", "TaskDownloadMissingSubtitles": "Hent manglende undertekster",
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.", "TaskUpdatePluginsDescription": "Henter og installerer opdateringer for plugins, som er indstillet til at blive opdateret automatisk.",
"TaskUpdatePlugins": "Opdater Plugins", "TaskUpdatePlugins": "Opdater Plugins",
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.", "TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gamle.",
"TaskCleanLogs": "Ryd Log Mappe", "TaskCleanLogs": "Ryd Log mappe",
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.", "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdateret metadata.",
"TaskRefreshLibrary": "Scan Medie Bibliotek", "TaskRefreshLibrary": "Scan Medie Bibliotek",
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.", "TaskCleanCacheDescription": "Sletter cache filer som systemet ikke længere bruger.",
"TaskCleanCache": "Ryd Cache Mappe", "TaskCleanCache": "Ryd Cache mappe",
"TasksChannelsCategory": "Internet Kanaler", "TasksChannelsCategory": "Internet Kanaler",
"TasksApplicationCategory": "Applikation", "TasksApplicationCategory": "Applikation",
"TasksLibraryCategory": "Bibliotek", "TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Vedligeholdelse", "TasksMaintenanceCategory": "Vedligeholdelse",
"TaskRefreshChapterImages": "Udtræk Kapitel billeder", "TaskRefreshChapterImages": "Udtræk kapitel billeder",
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.", "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
"TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.", "TaskRefreshChannelsDescription": "Opdater internet kanal information.",
"TaskRefreshChannels": "Genopfrisk Kanaler", "TaskRefreshChannels": "Opdater Kanaler",
"TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.", "TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end 1 dag gammel.",
"TaskCleanTranscode": "Rengør Transcode Mappen", "TaskCleanTranscode": "Tøm Transcode mappen",
"TaskRefreshPeople": "Genopfrisk Personer", "TaskRefreshPeople": "Opdater Personer",
"TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek.", "TaskRefreshPeopleDescription": "Opdaterer metadata for skuespillere og instruktører i dit mediebibliotek.",
"TaskCleanActivityLogDescription": "Sletter linjer i aktivitetsloggen ældre end den konfigureret alder.", "TaskCleanActivityLogDescription": "Sletter linjer i aktivitetsloggen ældre end den konfigurerede alder.",
"TaskCleanActivityLog": "Ryd Aktivitetslog", "TaskCleanActivityLog": "Ryd Aktivitetslog",
"Undefined": "Udefineret", "Undefined": "Udefineret",
"Forced": "Tvunget", "Forced": "Tvunget",
"Default": "Standard", "Default": "Standard",
"TaskOptimizeDatabaseDescription": "Kompakter database og forkorter fri plads. Ved at køre denne proces efter at scanne biblioteket eller efter at ændre noget som kunne have indflydelse på databasen, kan forbedre ydeevne.", "TaskOptimizeDatabaseDescription": "Komprimerer databasen og frigør plads. Denne handling køres efter at have scannet mediebiblioteket, eller efter at have lavet ændringer til databasen, for at højne ydeevnen.",
"TaskOptimizeDatabase": "Optimér database", "TaskOptimizeDatabase": "Optimér database",
"TaskKeyframeExtractorDescription": "Udtrækker billeder fra videofiler for at lave mere præcise HLS playlister. Denne opgave kan godt tage lang tid.", "TaskKeyframeExtractorDescription": "Udtrækker billeder fra videofiler for at lave mere præcise HLS playlister. Denne opgave kan tage lang tid.",
"TaskKeyframeExtractor": "Billedramme udtrækker", "TaskKeyframeExtractor": "Nøglebillede udtræk",
"External": "Ekstern", "External": "Ekstern",
"HearingImpaired": "Hørehæmmet" "HearingImpaired": "Hørehæmmet"
} }

View File

@ -31,7 +31,7 @@
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca", "ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
"Latest": "Último contenido en", "Latest": "Últimas",
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin", "MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}", "MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada", "MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",

View File

@ -118,7 +118,7 @@
"TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.", "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.",
"TaskCleanActivityLog": "Tyhjennä toimintahistoria", "TaskCleanActivityLog": "Tyhjennä toimintahistoria",
"Undefined": "Määrittelemätön", "Undefined": "Määrittelemätön",
"TaskOptimizeDatabaseDescription": "Tiivistää ja puhdistaa tietokannan. Tämän toiminnon suorittaminen kirjastojen skannauksen tai muiden tietokantaan liittyvien muutoksien jälkeen voi parantaa suorituskykyä.", "TaskOptimizeDatabaseDescription": "Tiivistää ja puhdistaa tietokannan. Tämän toiminnon suorittaminen kirjastopäivityksen tai muiden mahdollisten tietokantamuutosten jälkeen voi parantaa suorituskykyä.",
"TaskOptimizeDatabase": "Optimoi tietokanta", "TaskOptimizeDatabase": "Optimoi tietokanta",
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.", "TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
"TaskKeyframeExtractor": "Avainkuvien purkain", "TaskKeyframeExtractor": "Avainkuvien purkain",

View File

@ -119,5 +119,9 @@
"Undefined": "Hindi tiyak", "Undefined": "Hindi tiyak",
"Forced": "Sapilitan", "Forced": "Sapilitan",
"TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.", "TaskOptimizeDatabaseDescription": "Iko-compact ang database at ita-truncate ang free space. Ang pagpapatakbo ng gawaing ito pagkatapos ng pag-scan sa library o paggawa ng iba pang mga pagbabago na nagpapahiwatig ng mga pagbabago sa database ay maaaring magpa-improve ng performance.",
"TaskOptimizeDatabase": "I-optimize ang database" "TaskOptimizeDatabase": "I-optimize ang database",
"HearingImpaired": "Bingi",
"TaskKeyframeExtractor": "Tagabunot ng Keyframe",
"TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.",
"External": "External"
} }

View File

@ -73,5 +73,36 @@
"Songs": "गाने", "Songs": "गाने",
"UserStartedPlayingItemWithValues": "{0} {2} पर {1} खेल रहे हैं", "UserStartedPlayingItemWithValues": "{0} {2} पर {1} खेल रहे हैं",
"UserStoppedPlayingItemWithValues": "{0} ने {2} पर {1} खेलना खत्म किया", "UserStoppedPlayingItemWithValues": "{0} ने {2} पर {1} खेलना खत्म किया",
"StartupEmbyServerIsLoading": "जेलीफ़िन सर्वर लोड हो रहा है। कृपया शीघ्र ही पुन: प्रयास करें।" "StartupEmbyServerIsLoading": "जेलीफ़िन सर्वर लोड हो रहा है। कृपया शीघ्र ही पुन: प्रयास करें।",
"ServerNameNeedsToBeRestarted": "{0} रीस्टार्ट करने की आवश्यकता है",
"UserCreatedWithName": "उपयोगकर्ता {0} बनाया गया",
"UserDownloadingItemWithValues": "{0} डाउनलोड हो रहा है",
"UserOfflineFromDevice": "{0} {1} से डिस्कनेक्ट हो गया है",
"Undefined": "अनिर्धारित",
"UserOnlineFromDevice": "{0} {1} से ऑनलाइन है",
"Shows": "शो",
"UserPasswordChangedWithName": "उपयोगकर्ता {0} के लिए पासवर्ड बदल दिया गया है",
"UserDeletedWithName": "उपयोगकर्ता {0} हटा दिया गया",
"UserPolicyUpdatedWithName": "{0} के लिए उपयोगकर्ता नीति अपडेट कर दी गई है",
"User": "उपयोगकर्ता",
"SubtitleDownloadFailureFromForItem": "{1} के लिए {0} से उपशीर्षक डाउनलोड करने में विफल",
"ProviderValue": "प्रदाता: {0}",
"ScheduledTaskFailedWithName": "{0}असफल",
"UserLockedOutWithName": "उपयोगकर्ता {0} को लॉक आउट कर दिया गया है",
"System": "प्रणाली",
"TvShows": "टीवी शो",
"HearingImpaired": "मूक बधिर",
"ValueSpecialEpisodeName": "विशेष - {0}",
"TasksMaintenanceCategory": "रखरखाव",
"Sync": "समाकलयति",
"VersionNumber": "{0} पाठान्तर",
"ValueHasBeenAddedToLibrary": "{0} आपके माध्यम ग्रन्थालय में उपजात हो गया हैं",
"TasksLibraryCategory": "संग्रहालय",
"TaskOptimizeDatabase": "जानकारी प्रवृद्धि",
"TaskDownloadMissingSubtitles": "असमेत अनुलेख को अवाहरति करें",
"TaskRefreshLibrary": "माध्यम संग्राहत को छाने",
"TaskCleanActivityLog": "क्रियाकलाप लॉग साफ करें",
"TasksChannelsCategory": "इंटरनेट प्रणाली",
"TasksApplicationCategory": "अनुप्रयोग",
"TaskRefreshPeople": "लोगोकी जानकारी ताज़ी करें"
} }

View File

@ -37,8 +37,8 @@
"MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました", "MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました",
"MessageServerConfigurationUpdated": "サーバー設定が更新されました", "MessageServerConfigurationUpdated": "サーバー設定が更新されました",
"MixedContent": "ミックスコンテンツ", "MixedContent": "ミックスコンテンツ",
"Movies": "ムービー", "Movies": "映画",
"Music": "ミュージック", "Music": "音楽",
"MusicVideos": "ミュージックビデオ", "MusicVideos": "ミュージックビデオ",
"NameInstallFailed": "{0}のインストールに失敗しました", "NameInstallFailed": "{0}のインストールに失敗しました",
"NameSeasonNumber": "シーズン {0}", "NameSeasonNumber": "シーズン {0}",

View File

@ -120,5 +120,6 @@
"Default": "Noklusējuma", "Default": "Noklusējuma",
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.", "TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
"TaskOptimizeDatabase": "Optimizēt datubāzi", "TaskOptimizeDatabase": "Optimizēt datubāzi",
"External": "Ārējais" "External": "Ārējais",
"HearingImpaired": "Ar dzirdes traucējumiem"
} }

View File

@ -0,0 +1,6 @@
{
"Albums": "辑册",
"Artists": "艺人",
"AuthenticationSucceededWithUserName": "{0} 授之权矣",
"Books": "册"
}

View File

@ -122,5 +122,6 @@
"External": "बाहेरचा", "External": "बाहेरचा",
"DeviceOnlineWithName": "{0} कनेक्ट झाले", "DeviceOnlineWithName": "{0} कनेक्ट झाले",
"DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे", "DeviceOfflineWithName": "{0} डिस्कनेक्ट झाला आहे",
"AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत" "AuthenticationSucceededWithUserName": "{0} यशस्वीरित्या प्रमाणीकृत",
"HearingImpaired": "कर्णबधीर"
} }

View File

@ -39,7 +39,7 @@
"MixedContent": "Kandungan campuran", "MixedContent": "Kandungan campuran",
"Movies": "Filem-filem", "Movies": "Filem-filem",
"Music": "Muzik", "Music": "Muzik",
"MusicVideos": "Video muzik", "MusicVideos": "Video Muzik",
"NameInstallFailed": "{0} pemasangan gagal", "NameInstallFailed": "{0} pemasangan gagal",
"NameSeasonNumber": "Musim {0}", "NameSeasonNumber": "Musim {0}",
"NameSeasonUnknown": "Musim Tidak Diketahui", "NameSeasonUnknown": "Musim Tidak Diketahui",
@ -55,7 +55,7 @@
"NotificationOptionPluginInstalled": "Plugin telah dipasang", "NotificationOptionPluginInstalled": "Plugin telah dipasang",
"NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang", "NotificationOptionPluginUninstalled": "Plugin telah dinyahpasang",
"NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang", "NotificationOptionPluginUpdateInstalled": "Kemaskini plugin telah dipasang",
"NotificationOptionServerRestartRequired": "", "NotificationOptionServerRestartRequired": "Perlu mulakan semula server",
"NotificationOptionTaskFailed": "Kegagalan tugas berjadual", "NotificationOptionTaskFailed": "Kegagalan tugas berjadual",
"NotificationOptionUserLockedOut": "Pengguna telah dikunci", "NotificationOptionUserLockedOut": "Pengguna telah dikunci",
"NotificationOptionVideoPlayback": "Ulangmain video bermula", "NotificationOptionVideoPlayback": "Ulangmain video bermula",
@ -109,5 +109,20 @@
"TaskRefreshLibrary": "Imbas Perpustakaan Media", "TaskRefreshLibrary": "Imbas Perpustakaan Media",
"TaskRefreshChapterImagesDescription": "Membuat gambaran kecil untuk video yang mempunyai bab.", "TaskRefreshChapterImagesDescription": "Membuat gambaran kecil untuk video yang mempunyai bab.",
"TaskRefreshChapterImages": "Ekstrak Gambar-gambar Bab", "TaskRefreshChapterImages": "Ekstrak Gambar-gambar Bab",
"TaskCleanCacheDescription": "Menghapuskan fail cache yang tidak lagi diperlukan oleh sistem." "TaskCleanCacheDescription": "Menghapuskan fail cache yang tidak lagi diperlukan oleh sistem.",
"HearingImpaired": "Lemah Pendengaran",
"TaskRefreshPeopleDescription": "Kemas kini metadata untuk pelakon dan pengarah di dalam perpustakaan media.",
"TaskUpdatePluginsDescription": "Muat turun dan kemas kini plugin yang dikonfigurasi secara automatik.",
"TaskDownloadMissingSubtitlesDescription": "Cari sari kata yang hilang di internet, berdasarkan konfigurasi metadata.",
"TaskOptimizeDatabaseDescription": "Mampatkan pangkalan data dan potong ruang kosong. Pelaksanaan tugas ini selepas pengimbasan perpustakaan boleh membantu membaiki prestasi.",
"TaskRefreshChannels": "Segarkan Saluran-saluran",
"TaskUpdatePlugins": "Kemas kini plugin",
"TaskDownloadMissingSubtitles": "Muat turn sari kata yang tiada",
"TaskCleanTranscodeDescription": "Padam fail transkod yang lebih lama dari satu hari.",
"TaskRefreshChannelsDescription": "Segarkan maklumat saluran internet.",
"TaskCleanTranscode": "Bersihkan direktori transkod",
"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."
} }

View File

@ -8,7 +8,7 @@
"CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}", "CameraImageUploadedFrom": "Nieuwe camera-afbeelding toegevoegd vanaf {0}",
"Channels": "Kanalen", "Channels": "Kanalen",
"ChapterNameValue": "Hoofdstuk {0}", "ChapterNameValue": "Hoofdstuk {0}",
"Collections": "Verzamelingen", "Collections": "Collecties",
"DeviceOfflineWithName": "Verbinding met {0} is verbroken", "DeviceOfflineWithName": "Verbinding met {0} is verbroken",
"DeviceOnlineWithName": "{0} is verbonden", "DeviceOnlineWithName": "{0} is verbonden",
"FailedLoginAttemptWithUserName": "Mislukte inlogpoging van {0}", "FailedLoginAttemptWithUserName": "Mislukte inlogpoging van {0}",
@ -114,7 +114,7 @@
"TasksApplicationCategory": "Toepassing", "TasksApplicationCategory": "Toepassing",
"TasksLibraryCategory": "Bibliotheek", "TasksLibraryCategory": "Bibliotheek",
"TasksMaintenanceCategory": "Onderhoud", "TasksMaintenanceCategory": "Onderhoud",
"TaskCleanActivityLogDescription": "Verwijdert activiteiten logs ouder dan de ingestelde leeftijd.", "TaskCleanActivityLogDescription": "Verwijdert activiteitenlogs ouder dan de ingestelde leeftijd.",
"TaskCleanActivityLog": "Activiteitenlogboek legen", "TaskCleanActivityLog": "Activiteitenlogboek legen",
"Undefined": "Niet gedefinieerd", "Undefined": "Niet gedefinieerd",
"Forced": "Geforceerd", "Forced": "Geforceerd",

View File

@ -0,0 +1,4 @@
{
"External": "ବହିଃସ୍ଥ",
"Genres": "ଧରଣ"
}

View File

@ -28,22 +28,22 @@
"ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ", "ValueHasBeenAddedToLibrary": "{0} ਤੁਹਾਡੀ ਮੀਡੀਆ ਲਾਇਬ੍ਰੇਰੀ ਵਿੱਚ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ", "UserStoppedPlayingItemWithValues": "{0} ਨੇ {2} 'ਤੇ {1} ਖੇਡਣਾ ਪੂਰਾ ਕਰ ਲਿਆ ਹੈ",
"UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ", "UserStartedPlayingItemWithValues": "{0} {2} 'ਤੇ {1} ਖੇਡ ਰਿਹਾ ਹੈ",
"UserPolicyUpdatedWithName": "ਉਪਭੋਗਤਾ ਨੀਤੀ ਨੂੰ {0} ਲਈ ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ", "UserPolicyUpdatedWithName": "ਵਰਤੋਂਕਾਰ ਨੀਤੀ ਨੂੰ {0} ਲਈ ਅਪਡੇਟ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserPasswordChangedWithName": "ਪਾਸਵਰਡ ਯੂਜ਼ਰ ਲਈ ਬਦਲਿਆ ਗਿਆ ਹੈ {0}", "UserPasswordChangedWithName": "{0} ਵਰਤੋਂਕਾਰ ਲਈ ਪਾਸਵਰਡ ਬਦਲਿਆ ਗਿਆ ਸੀ",
"UserOnlineFromDevice": "{0} ਤੋਂ isਨਲਾਈਨ ਹੈ {1}", "UserOnlineFromDevice": "{0} ਨੂੰ {1} ਤੋਂ ਆਨਲਾਈਨ ਹੈ",
"UserOfflineFromDevice": "{0} ਤੋਂ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ {1}", "UserOfflineFromDevice": "{0} ਤੋਂ ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ {1}",
"UserLockedOutWithName": "ਯੂਜ਼ਰ {0} ਨੂੰ ਲਾਕ ਆਉਟ ਕਰ ਦਿੱਤਾ ਗਿਆ ਹੈ", "UserLockedOutWithName": "ਵਰਤੋਂਕਾਰ {0} ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ ਹੈ",
"UserDownloadingItemWithValues": "{0} ਡਾ{ਨਲੋਡ ਕਰ ਰਿਹਾ ਹੈ {1}", "UserDownloadingItemWithValues": "{0} {1} ਨੂੰ ਡਾਊਨਲੋਡ ਕਰ ਰਿਹਾ ਹੈ",
"UserDeletedWithName": "ਯੂਜ਼ਰ {0} ਨੂੰ ਮਿਟਾ ਦਿੱਤਾ ਗਿਆ ਹੈ", "UserDeletedWithName": "ਵਰਤੋਂਕਾਰ {0} ਨੂੰ ਹਟਾਇਆ ਗਿਆ",
"UserCreatedWithName": "ਯੂਜ਼ਰ {0} ਬਣਾਇਆ ਗਿਆ ਹੈ", "UserCreatedWithName": "ਵਰਤੋਂਕਾਰ {0} ਬਣਾਇਆ ਗਿਆ ਹੈ",
"User": "ਯੂਜ਼ਰ", "User": "ਵਰਤੋਂਕਾਰ",
"Undefined": "ਪਰਿਭਾਸ਼ਤ", "Undefined": "ਪਰਿਭਾਸ਼ਤ",
"TvShows": "ਟੀਵੀ ਸ਼ੋਅਜ਼", "TvShows": "ਟੀਵੀ ਸ਼ੋਅ",
"System": "ਸਿਸਟਮ", "System": "ਸਿਸਟਮ",
"Sync": "ਸਿੰਕ", "Sync": "ਸਿੰਕ",
"SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾ toਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ", "SubtitleDownloadFailureFromForItem": "ਉਪਸਿਰਲੇਖ {1} ਲਈ {0} ਤੋਂ ਡਾਨਲੋਡ ਕਰਨ ਵਿੱਚ ਅਸਫਲ ਰਹੇ",
"StartupEmbyServerIsLoading": "ਜੈਲੀਫਿਨ ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ. ਕਿਰਪਾ ਕਰਕੇ ਜਲਦੀ ਹੀ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ.", "StartupEmbyServerIsLoading": "Jellyfin ਸਰਵਰ ਲੋਡ ਹੋ ਰਿਹਾ ਹੈ। ਛੇਤੀ ਹੀ ਫ਼ੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ।",
"Songs": "ਗਾਣੇ", "Songs": "ਗਾਣੇ",
"Shows": "ਸ਼ੋਅ", "Shows": "ਸ਼ੋਅ",
"ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", "ServerNameNeedsToBeRestarted": "{0} ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
"ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ", "ScheduledTaskStartedWithName": "{0} ਸ਼ੁਰੂ ਹੋਇਆ",
@ -57,12 +57,12 @@
"Photos": "ਫੋਟੋਆਂ", "Photos": "ਫੋਟੋਆਂ",
"NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ", "NotificationOptionVideoPlaybackStopped": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਰੋਕਿਆ ਗਿਆ",
"NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ", "NotificationOptionVideoPlayback": "ਵੀਡੀਓ ਪਲੇਬੈਕ ਸ਼ੁਰੂ ਹੋਇਆ",
"NotificationOptionUserLockedOut": "ਉਪਭੋਗਤਾ ਨੂੰ ਲਾਕ ਆਉਟ ਕੀਤਾ ਗਿਆ", "NotificationOptionUserLockedOut": "ਵਰਤੋਂਕਾਰ ਨੂੰ ਲਾਕ ਕੀਤਾ",
"NotificationOptionTaskFailed": "ਨਿਰਧਾਰਤ ਕਾਰਜ ਅਸਫਲਤਾ", "NotificationOptionTaskFailed": "ਨਿਰਧਾਰਤ ਕਾਰਜ ਅਸਫਲਤਾ",
"NotificationOptionServerRestartRequired": "ਸਰਵਰ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ", "NotificationOptionServerRestartRequired": "ਸਰਵਰ ਨੂੰ ਮੁੜ ਚਾਲੂ ਕਰਨ ਦੀ ਲੋੜ ਹੈ",
"NotificationOptionPluginUpdateInstalled": "ਪਲੱਗਇਨ ਅਪਡੇਟ ਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ", "NotificationOptionPluginUpdateInstalled": "ਪਲੱਗਇਨ ਅਪਡੇਟ ਇੰਸਟੌਲ ਕੀਤਾ ਗਿਆ",
"NotificationOptionPluginUninstalled": "ਪਲੱਗਇਨ ਅਣਇੰਸਟੌਲ ਕੀਤਾ", "NotificationOptionPluginUninstalled": "ਪਲੱਗਇਨ ਅਣਇੰਸਟੌਲ ਕੀਤਾ",
"NotificationOptionPluginInstalled": "ਪਲੱਗਇਨ ਸਥਾਪਿਤ ਕੀਤਾ", "NotificationOptionPluginInstalled": "ਪਲੱਗਇਨ ਇੰਸਟਾਲ ਕੀਤੀ",
"NotificationOptionPluginError": "ਪਲੱਗਇਨ ਅਸਫਲ", "NotificationOptionPluginError": "ਪਲੱਗਇਨ ਅਸਫਲ",
"NotificationOptionNewLibraryContent": "ਨਵੀਂ ਸਮੱਗਰੀ ਸ਼ਾਮਲ ਕੀਤੀ ਗਈ", "NotificationOptionNewLibraryContent": "ਨਵੀਂ ਸਮੱਗਰੀ ਸ਼ਾਮਲ ਕੀਤੀ ਗਈ",
"NotificationOptionInstallationFailed": "ਇੰਸਟਾਲੇਸ਼ਨ ਅਸਫਲ", "NotificationOptionInstallationFailed": "ਇੰਸਟਾਲੇਸ਼ਨ ਅਸਫਲ",
@ -92,7 +92,7 @@
"HomeVideos": "ਘਰੇਲੂ ਵੀਡੀਓ", "HomeVideos": "ਘਰੇਲੂ ਵੀਡੀਓ",
"HeaderRecordingGroups": "ਰਿਕਾਰਡਿੰਗ ਸਮੂਹ", "HeaderRecordingGroups": "ਰਿਕਾਰਡਿੰਗ ਸਮੂਹ",
"HeaderNextUp": "ਅੱਗੇ", "HeaderNextUp": "ਅੱਗੇ",
"HeaderLiveTV": "ਲਾਈਵ ਟੀ", "HeaderLiveTV": "ਲਾਈਵ ਟੀਵੀ",
"HeaderFavoriteSongs": "ਮਨਪਸੰਦ ਗਾਣੇ", "HeaderFavoriteSongs": "ਮਨਪਸੰਦ ਗਾਣੇ",
"HeaderFavoriteShows": "ਮਨਪਸੰਦ ਸ਼ੋਅ", "HeaderFavoriteShows": "ਮਨਪਸੰਦ ਸ਼ੋਅ",
"HeaderFavoriteEpisodes": "ਮਨਪਸੰਦ ਐਪੀਸੋਡ", "HeaderFavoriteEpisodes": "ਮਨਪਸੰਦ ਐਪੀਸੋਡ",
@ -102,20 +102,22 @@
"HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ", "HeaderAlbumArtists": "ਐਲਬਮ ਕਲਾਕਾਰ",
"Genres": "ਸ਼ੈਲੀਆਂ", "Genres": "ਸ਼ੈਲੀਆਂ",
"Forced": "ਮਜਬੂਰ", "Forced": "ਮਜਬੂਰ",
"Folders": "ਫੋਲਡਰ", "Folders": "ਫੋਲਡਰ",
"Favorites": "ਮਨਪਸੰਦ", "Favorites": "ਮਨਪਸੰਦ",
"FailedLoginAttemptWithUserName": "ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ {0}", "FailedLoginAttemptWithUserName": "{0} ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ",
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ", "DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ", "DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
"Default": "ਡਿਫੌਲਟ", "Default": "ਡਿਫੌਲਟ",
"Collections": "ਸੰਗ੍ਰਹਿਣ", "Collections": "ਸੰਗ੍ਰਹਿਣ",
"ChapterNameValue": "ਅਧਿਆਇ {0}", "ChapterNameValue": "ਚੈਪਟਰ {0}",
"Channels": "ਚੈਨਲ", "Channels": "ਚੈਨਲ",
"CameraImageUploadedFrom": "ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ {0}", "CameraImageUploadedFrom": "{0} ਤੋਂ ਇੱਕ ਨਵਾਂ ਕੈਮਰਾ ਚਿੱਤਰ ਅਪਲੋਡ ਕੀਤਾ ਗਿਆ ਹੈ",
"Books": "ਕਿਤਾਬਾਂ", "Books": "ਕਿਤਾਬਾਂ",
"AuthenticationSucceededWithUserName": "{0} ਸਫਲਤਾਪੂਰਕ ਪ੍ਰਮਾਣਿਤ", "AuthenticationSucceededWithUserName": "{0} ਸਫਲਤਾਪੂਰਕ ਪ੍ਰਮਾਣਿਤ",
"Artists": "ਕਲਾਕਾਰ", "Artists": "ਕਲਾਕਾਰ",
"Application": "ਐਪਲੀਕੇਸ਼ਨ", "Application": "ਐਪਲੀਕੇਸ਼ਨ",
"AppDeviceValues": "ਐਪ: {0}, ਜੰਤਰ: {1}", "AppDeviceValues": "ਐਪ: {0}, ਜੰਤਰ: {1}",
"Albums": "ਐਲਬਮਾਂ" "Albums": "ਐਲਬਮਾਂ",
"TaskOptimizeDatabase": "ਡਾਟਾਬੇਸ ਅਨੁਕੂਲ ਬਣਾਓ",
"External": "ਬਾਹਰੀ"
} }

View File

@ -121,5 +121,7 @@
"TaskOptimizeDatabase": "Otimizar base de dados", "TaskOptimizeDatabase": "Otimizar base de dados",
"TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.", "TaskOptimizeDatabaseDescription": "Base de dados compacta e corta espaço livre. A execução desta tarefa depois de digitalizar a biblioteca ou de fazer outras alterações que impliquem modificações na base de dados pode melhorar o desempenho.",
"External": "Externo", "External": "Externo",
"HearingImpaired": "Problemas auditivos" "HearingImpaired": "Problemas auditivos",
"TaskKeyframeExtractor": "Extrator de quadro-chave",
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo."
} }

View File

@ -42,7 +42,7 @@
"MusicVideos": "Муз. видео", "MusicVideos": "Муз. видео",
"NameInstallFailed": "Установка {0} неудачна", "NameInstallFailed": "Установка {0} неудачна",
"NameSeasonNumber": "Сезон {0}", "NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Сезон неопознан", "NameSeasonUnknown": "Сезон не опознан",
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.", "NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения", "NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено", "NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
@ -96,7 +96,7 @@
"TaskRefreshChannels": "Обновление каналов", "TaskRefreshChannels": "Обновление каналов",
"TaskCleanTranscode": "Очистка каталога перекодировки", "TaskCleanTranscode": "Очистка каталога перекодировки",
"TaskUpdatePlugins": "Обновление плагинов", "TaskUpdatePlugins": "Обновление плагинов",
"TaskRefreshPeople": "Подновление людей", "TaskRefreshPeople": "Обновление информации о персонах",
"TaskCleanLogs": "Очистка каталога журналов", "TaskCleanLogs": "Очистка каталога журналов",
"TaskRefreshLibrary": "Сканирование медиатеки", "TaskRefreshLibrary": "Сканирование медиатеки",
"TaskRefreshChapterImages": "Извлечение изображений сцен", "TaskRefreshChapterImages": "Извлечение изображений сцен",

View File

@ -12,7 +12,7 @@
"HeaderContinueWatching": "دیکھنا جاری رکھیں", "HeaderContinueWatching": "دیکھنا جاری رکھیں",
"Playlists": "پلے لسٹس", "Playlists": "پلے لسٹس",
"ValueSpecialEpisodeName": "خصوصی - {0}", "ValueSpecialEpisodeName": "خصوصی - {0}",
"Shows": "دکھاتا ہے۔", "Shows": "دکھاتا ہے",
"Genres": "انواع", "Genres": "انواع",
"Artists": "فنکار", "Artists": "فنکار",
"Sync": "مطابقت پذیری", "Sync": "مطابقت پذیری",
@ -123,5 +123,5 @@
"TaskCleanActivityLogDescription": "تشکیل شدہ عمر سے زیادہ پرانی سرگرمی لاگ اندراجات کو حذف کرتا ہے۔", "TaskCleanActivityLogDescription": "تشکیل شدہ عمر سے زیادہ پرانی سرگرمی لاگ اندراجات کو حذف کرتا ہے۔",
"External": "بیرونی", "External": "بیرونی",
"HearingImpaired": "قوت سماعت سے محروم", "HearingImpaired": "قوت سماعت سے محروم",
"TaskCleanActivityLog": "سرگرمی لاگ کو صاف کریں۔" "TaskCleanActivityLog": "سرگرمی لاگ کو صاف کریں"
} }

View File

@ -14,7 +14,7 @@
"FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败", "FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败",
"Favorites": "我的最爱", "Favorites": "我的最爱",
"Folders": "文件夹", "Folders": "文件夹",
"Genres": "风格", "Genres": "类型",
"HeaderAlbumArtists": "专辑艺术家", "HeaderAlbumArtists": "专辑艺术家",
"HeaderContinueWatching": "继续观看", "HeaderContinueWatching": "继续观看",
"HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteAlbums": "收藏的专辑",

View File

@ -4,13 +4,13 @@
"Application": "應用程式", "Application": "應用程式",
"Artists": "藝人", "Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} 授權成功", "AuthenticationSucceededWithUserName": "{0} 授權成功",
"Books": "書", "Books": "",
"CameraImageUploadedFrom": "{0} 成功上傳一張新片", "CameraImageUploadedFrom": "{0} 成功上傳一張新片",
"Channels": "頻道", "Channels": "頻道",
"ChapterNameValue": "章節 {0}", "ChapterNameValue": "第 {0} 章",
"Collections": "合輯", "Collections": "系列",
"DeviceOfflineWithName": "{0} 已斷開連接", "DeviceOfflineWithName": "{0} 已斷開連接",
"DeviceOnlineWithName": "{0} 已連接", "DeviceOnlineWithName": "{0} 已連接",
"FailedLoginAttemptWithUserName": "{0} 登入失敗", "FailedLoginAttemptWithUserName": "{0} 登入失敗",
"Favorites": "我的最愛", "Favorites": "我的最愛",
"Folders": "資料夾", "Folders": "資料夾",
@ -23,105 +23,105 @@
"HeaderFavoriteShows": "最愛的節目", "HeaderFavoriteShows": "最愛的節目",
"HeaderFavoriteSongs": "最愛的歌曲", "HeaderFavoriteSongs": "最愛的歌曲",
"HeaderLiveTV": "電視直播", "HeaderLiveTV": "電視直播",
"HeaderNextUp": "接下來", "HeaderNextUp": "接著播放",
"HeaderRecordingGroups": "錄製組", "HeaderRecordingGroups": "錄製組",
"HomeVideos": "家庭影片", "HomeVideos": "家庭影片",
"Inherit": "繼承", "Inherit": "繼承",
"ItemAddedWithName": "{0} 已添加至媒體庫", "ItemAddedWithName": "{0} 已添加至媒體庫",
"ItemRemovedWithName": "{0} 已從媒體庫移除", "ItemRemovedWithName": "{0} 已從媒體庫移除",
"LabelIpAddressValue": "IP 地址: {0}", "LabelIpAddressValue": "IP 地址: {0}",
"LabelRunningTimeValue": "運行時間: {0}", "LabelRunningTimeValue": "運行時間: {0}",
"Latest": "最新", "Latest": "最新",
"MessageApplicationUpdated": "Jellyfin 伺服器已更新", "MessageApplicationUpdated": "Jellyfin 更新",
"MessageApplicationUpdatedTo": "Jellyfin 伺服器已更新至 {0}", "MessageApplicationUpdatedTo": "Jellyfin 更新至 {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已更新", "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 已更新",
"MessageServerConfigurationUpdated": "伺服器設定已經更新", "MessageServerConfigurationUpdated": "伺服器設定已經更新",
"MixedContent": "混合內容", "MixedContent": "混合內容",
"Movies": "電影", "Movies": "電影",
"Music": "音樂", "Music": "音樂",
"MusicVideos": "音樂影片", "MusicVideos": "MV",
"NameInstallFailed": "{0} 安裝失敗", "NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季", "NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數", "NameSeasonUnknown": "未知的季度",
"NewVersionIsAvailable": "新版本的 Jellyfin 伺服器可供下載。", "NewVersionIsAvailable": "有較新版本的 Jellyfin 可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的更新", "NotificationOptionApplicationUpdateAvailable": "有可用的更新",
"NotificationOptionApplicationUpdateInstalled": "應用程式已更新", "NotificationOptionApplicationUpdateInstalled": "應用程式已更新",
"NotificationOptionAudioPlayback": "開始播放音訊", "NotificationOptionAudioPlayback": "開始播放音訊",
"NotificationOptionAudioPlaybackStopped": "停止播放音訊", "NotificationOptionAudioPlaybackStopped": "停止播放音訊",
"NotificationOptionCameraImageUploaded": "相片已上傳", "NotificationOptionCameraImageUploaded": "相片已上傳",
"NotificationOptionInstallationFailed": "安裝失敗", "NotificationOptionInstallationFailed": "安裝失敗",
"NotificationOptionNewLibraryContent": "已添加新内容", "NotificationOptionNewLibraryContent": "已添加新内容",
"NotificationOptionPluginError": "擴充元件錯誤", "NotificationOptionPluginError": "插件出現錯誤",
"NotificationOptionPluginInstalled": "擴充元件已安裝", "NotificationOptionPluginInstalled": "插件已被安裝",
"NotificationOptionPluginUninstalled": "擴充元件已移除", "NotificationOptionPluginUninstalled": "插件已被移除",
"NotificationOptionPluginUpdateInstalled": "擴充元件更新已安裝", "NotificationOptionPluginUpdateInstalled": "插件已被更新",
"NotificationOptionServerRestartRequired": "伺服器需要重", "NotificationOptionServerRestartRequired": "伺服器需要重",
"NotificationOptionTaskFailed": "計劃任務失敗", "NotificationOptionTaskFailed": "排程任務執行失敗",
"NotificationOptionUserLockedOut": "用家已鎖定", "NotificationOptionUserLockedOut": "用戶已被鎖定",
"NotificationOptionVideoPlayback": "開始播放視頻", "NotificationOptionVideoPlayback": "開始播放影片",
"NotificationOptionVideoPlaybackStopped": "已停止播放視頻", "NotificationOptionVideoPlaybackStopped": "已停止播放影片",
"Photos": "相片", "Photos": "相片",
"Playlists": "播放清單", "Playlists": "播放清單",
"Plugin": "插件", "Plugin": "插件",
"PluginInstalledWithName": "已安裝 {0}", "PluginInstalledWithName": "已安裝 {0}",
"PluginUninstalledWithName": "已移除 {0}", "PluginUninstalledWithName": "已移除 {0}",
"PluginUpdatedWithName": "已更新 {0}", "PluginUpdatedWithName": "已更新 {0}",
"ProviderValue": "提供者: {0}", "ProviderValue": "提供者{0}",
"ScheduledTaskFailedWithName": "{0} 任務失敗", "ScheduledTaskFailedWithName": "{0} 執行失敗",
"ScheduledTaskStartedWithName": "{0} 任務開始", "ScheduledTaskStartedWithName": "{0} 開始執行",
"ServerNameNeedsToBeRestarted": "{0} 需要重", "ServerNameNeedsToBeRestarted": "{0} 需要重",
"Shows": "節目", "Shows": "節目",
"Songs": "歌曲", "Songs": "歌曲",
"StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。", "StartupEmbyServerIsLoading": "正在載入 Jellyfin請稍後再試。",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕", "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕",
"Sync": "同步", "Sync": "同步",
"System": "系統", "System": "系統",
"TvShows": "電視節目", "TvShows": "電視節目",
"User": "使用者", "User": "用戶",
"UserCreatedWithName": "使用者 {0} 已創建", "UserCreatedWithName": "用戶 {0} 已被建立",
"UserDeletedWithName": "使用者 {0} 已移除", "UserDeletedWithName": "用戶 {0} 已被移除",
"UserDownloadingItemWithValues": "{0} 正在下載 {1}", "UserDownloadingItemWithValues": "{0} 正在下載 {1}",
"UserLockedOutWithName": "使用者 {0} 已被鎖定", "UserLockedOutWithName": "使用者 {0} 已被鎖定",
"UserOfflineFromDevice": "{0} 從 {1} 斷開", "UserOfflineFromDevice": "{0} 從 {1} 斷開連接",
"UserOnlineFromDevice": "{0} 已連綫,來自 {1}", "UserOnlineFromDevice": "{0} 從 {1} 連線",
"UserPasswordChangedWithName": "使用者 {0} 的密碼已變更", "UserPasswordChangedWithName": "{0} 的密碼已被變改",
"UserPolicyUpdatedWithName": "使用者協議已更新為 {0}", "UserPolicyUpdatedWithName": "使用者協議已更新為 {0}",
"UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}", "UserStartedPlayingItemWithValues": "{0} 正在 {2} 上播放 {1}",
"UserStoppedPlayingItemWithValues": "{0} 已在 {2} 上停止播放 {1}", "UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 上播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已添加到你的媒體庫", "ValueHasBeenAddedToLibrary": "已添加 {0} 到你的媒體庫",
"ValueSpecialEpisodeName": "特典 - {0}", "ValueSpecialEpisodeName": "特典 - {0}",
"VersionNumber": "版本{0}", "VersionNumber": "版本 {0}",
"TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskDownloadMissingSubtitles": "下載缺少的字幕",
"TaskUpdatePlugins": "更新插件", "TaskUpdatePlugins": "更新插件",
"TasksApplicationCategory": "應用程式", "TasksApplicationCategory": "應用程式",
"TaskRefreshLibraryDescription": "掃描媒體庫以查找新文件並刷新metadata。", "TaskRefreshLibraryDescription": "掃描媒體庫以加入新增檔案及重新載入 metadata。",
"TasksMaintenanceCategory": "維護", "TasksMaintenanceCategory": "維護",
"TaskDownloadMissingSubtitlesDescription": "根據metadata配置在互聯網上搜索缺少的字幕。", "TaskDownloadMissingSubtitlesDescription": "根據元數據中的設定,在互聯網上搜索缺少的字幕。",
"TaskRefreshChannelsDescription": "刷新互聯網頻道信息。", "TaskRefreshChannelsDescription": "重新載入網絡頻道的資訊。",
"TaskRefreshChannels": "刷新頻道", "TaskRefreshChannels": "重新載入頻道",
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。", "TaskCleanTranscodeDescription": "刪除超過一天的轉碼文件。",
"TaskCleanTranscode": "清理轉碼目錄", "TaskCleanTranscode": "清理轉碼目錄",
"TaskUpdatePluginsDescription": "下載並安裝配置為自動更新的插件的更新。", "TaskUpdatePluginsDescription": "下載並更新能夠被自動更新的插件。",
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的元數據。", "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的元數據。",
"TaskCleanLogsDescription": "刪除超過{0}天的日誌文件。", "TaskCleanLogsDescription": "刪除超過{0}天的紀錄檔。",
"TaskCleanLogs": "清理日誌目錄", "TaskCleanLogs": "清理紀錄檔目錄",
"TaskRefreshLibrary": "掃描媒體庫", "TaskRefreshLibrary": "掃描媒體庫",
"TaskRefreshChapterImagesDescription": "為帶有章節的視頻創建縮略圖。", "TaskRefreshChapterImagesDescription": "為帶有章節的影片建立縮圖。",
"TaskRefreshChapterImages": "提取章節圖像", "TaskRefreshChapterImages": "提取章節圖像",
"TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。", "TaskCleanCacheDescription": "刪除系統不再需要的緩存文件。",
"TaskCleanCache": "清理緩存目錄", "TaskCleanCache": "清理緩存目錄",
"TasksChannelsCategory": "互聯網頻道", "TasksChannelsCategory": "頻道",
"TasksLibraryCategory": "庫", "TasksLibraryCategory": "庫",
"TaskRefreshPeople": "刷新人物", "TaskRefreshPeople": "重新載入人物",
"TaskCleanActivityLog": "清理活動記錄", "TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義", "Undefined": "未定義",
"Forced": "強制", "Forced": "強制",
"Default": "預設", "Default": "預設",
"TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。", "TaskOptimizeDatabaseDescription": "壓縮數據庫並截斷可用空間。在掃描媒體庫或執行其他數據庫的修改後運行此任務可能會提高性能。",
"TaskOptimizeDatabase": "最佳化數據庫", "TaskOptimizeDatabase": "最佳化數據庫",
"TaskCleanActivityLogDescription": "刪除早於設定時間的日誌記錄。", "TaskCleanActivityLogDescription": "刪除早於設定時間的活動記錄。",
"TaskKeyframeExtractorDescription": "提取關鍵格以創建更準確的HLS播放列表。次指示可能用時很長。", "TaskKeyframeExtractorDescription": "提取關鍵幀以建立更準確的 HLS 播放列表。此工作或需要使用較長時間來完成。",
"TaskKeyframeExtractor": "關鍵幀提取器", "TaskKeyframeExtractor": "關鍵幀提取器",
"External": "外部", "External": "外部",
"HearingImpaired": "聽力障礙" "HearingImpaired": "聽力障礙"

View File

@ -91,14 +91,14 @@
"HeaderRecordingGroups": "錄製組", "HeaderRecordingGroups": "錄製組",
"Inherit": "繼承", "Inherit": "繼承",
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕", "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕",
"TaskDownloadMissingSubtitlesDescription": "透過中繼資料從網路上搜尋遺失的字幕。", "TaskDownloadMissingSubtitlesDescription": "透過媒體資訊從網路上搜尋遺失的字幕。",
"TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道", "TaskRefreshChannels": "重新整理頻道",
"TaskUpdatePlugins": "更新附加元件", "TaskUpdatePlugins": "更新附加元件",
"TaskRefreshPeople": "更新人物", "TaskRefreshPeople": "更新人物",
"TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。", "TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。",
"TaskCleanLogs": "清空日誌資料夾", "TaskCleanLogs": "清空日誌資料夾",
"TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新中繼資料。", "TaskRefreshLibraryDescription": "重新掃描媒體庫的新檔案並更新媒體資訊。",
"TaskRefreshLibrary": "重新掃描媒體庫", "TaskRefreshLibrary": "重新掃描媒體庫",
"TaskRefreshChapterImages": "擷取章節圖片", "TaskRefreshChapterImages": "擷取章節圖片",
"TaskCleanCacheDescription": "刪除系統已不需要的快取。", "TaskCleanCacheDescription": "刪除系統已不需要的快取。",
@ -108,7 +108,7 @@
"TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。", "TaskCleanTranscodeDescription": "刪除超過一天的轉碼檔案。",
"TaskCleanTranscode": "清除轉碼資料夾", "TaskCleanTranscode": "清除轉碼資料夾",
"TaskUpdatePluginsDescription": "為已設置為自動更新的附加元件下載並安裝更新。", "TaskUpdatePluginsDescription": "為已設置為自動更新的附加元件下載並安裝更新。",
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的中繼資料。", "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的資訊。",
"TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。", "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。",
"TasksChannelsCategory": "網路頻道", "TasksChannelsCategory": "網路頻道",
"TasksApplicationCategory": "應用程式", "TasksApplicationCategory": "應用程式",

View File

@ -198,25 +198,25 @@ namespace Emby.Server.Implementations.Localization
} }
// Minimum rating possible // Minimum rating possible
if (!ratings.Any(x => x.Value == 0)) if (ratings.All(x => x.Value != 0))
{ {
ratings.Add(new ParentalRating("Approved", 0)); ratings.Add(new ParentalRating("Approved", 0));
} }
// Matches PG (this has different age restrictions depending on country) // Matches PG (this has different age restrictions depending on country)
if (!ratings.Any(x => x.Value == 10)) if (ratings.All(x => x.Value != 10))
{ {
ratings.Add(new ParentalRating("10", 10)); ratings.Add(new ParentalRating("10", 10));
} }
// Matches PG-13 // Matches PG-13
if (!ratings.Any(x => x.Value == 13)) if (ratings.All(x => x.Value != 13))
{ {
ratings.Add(new ParentalRating("13", 13)); ratings.Add(new ParentalRating("13", 13));
} }
// Matches TV-14 // Matches TV-14
if (!ratings.Any(x => x.Value == 14)) if (ratings.All(x => x.Value != 14))
{ {
ratings.Add(new ParentalRating("14", 14)); ratings.Add(new ParentalRating("14", 14));
} }
@ -229,13 +229,13 @@ namespace Emby.Server.Implementations.Localization
} }
// A lot of countries don't excplicitly have a seperate rating for adult content // A lot of countries don't excplicitly have a seperate rating for adult content
if (!ratings.Any(x => x.Value == 1000)) if (ratings.All(x => x.Value != 1000))
{ {
ratings.Add(new ParentalRating("XXX", 1000)); ratings.Add(new ParentalRating("XXX", 1000));
} }
// A lot of countries don't excplicitly have a seperate rating for banned content // A lot of countries don't excplicitly have a seperate rating for banned content
if (!ratings.Any(x => x.Value == 1001)) if (ratings.All(x => x.Value != 1001))
{ {
ratings.Add(new ParentalRating("Banned", 1001)); ratings.Add(new ParentalRating("Banned", 1001));
} }

View File

@ -15,6 +15,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -62,23 +63,16 @@ namespace Emby.Server.Implementations.MediaEncoder
/// Determines whether [is eligible for chapter image extraction] [the specified video]. /// Determines whether [is eligible for chapter image extraction] [the specified video].
/// </summary> /// </summary>
/// <param name="video">The video.</param> /// <param name="video">The video.</param>
/// <param name="libraryOptions">The library options for the video.</param>
/// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns> /// <returns><c>true</c> if [is eligible for chapter image extraction] [the specified video]; otherwise, <c>false</c>.</returns>
private bool IsEligibleForChapterImageExtraction(Video video) private bool IsEligibleForChapterImageExtraction(Video video, LibraryOptions libraryOptions)
{ {
if (video.IsPlaceHolder) if (video.IsPlaceHolder)
{ {
return false; return false;
} }
var libraryOptions = _libraryManager.GetLibraryOptions(video); if (libraryOptions is null || !libraryOptions.EnableChapterImageExtraction)
if (libraryOptions is not null)
{
if (!libraryOptions.EnableChapterImageExtraction)
{
return false;
}
}
else
{ {
return false; return false;
} }
@ -99,7 +93,9 @@ namespace Emby.Server.Implementations.MediaEncoder
public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) public async Task<bool> RefreshChapterImages(Video video, IDirectoryService directoryService, IReadOnlyList<ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken)
{ {
if (!IsEligibleForChapterImageExtraction(video)) var libraryOptions = _libraryManager.GetLibraryOptions(video);
if (!IsEligibleForChapterImageExtraction(video, libraryOptions))
{ {
extractImages = false; extractImages = false;
} }
@ -179,6 +175,12 @@ namespace Emby.Server.Implementations.MediaEncoder
chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path);
changesMade = true; changesMade = true;
} }
else if (libraryOptions?.EnableChapterImageExtraction != true)
{
// We have an image for the current chapter but the user has disabled chapter image extraction -> delete this chapter's image
chapter.ImagePath = null;
changesMade = true;
}
} }
if (saveChapters && changesMade) if (saveChapters && changesMade)

View File

@ -135,16 +135,8 @@ namespace Emby.Server.Implementations.Playlists
{ {
Name = name, Name = name,
Path = path, Path = path,
Shares = new[] OwnerUserId = options.UserId,
{ Shares = options.Shares ?? Array.Empty<Share>()
new Share
{
UserId = options.UserId.Equals(default)
? null
: options.UserId.ToString("N", CultureInfo.InvariantCulture),
CanEdit = true
}
}
}; };
playlist.SetMediaType(options.MediaType); playlist.SetMediaType(options.MediaType);
@ -537,5 +529,55 @@ namespace Emby.Server.Implementations.Playlists
return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ?? return _libraryManager.RootFolder.Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)) ??
_libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal)); _libraryManager.GetUserRootFolder().Children.OfType<Folder>().FirstOrDefault(i => string.Equals(i.GetType().Name, TypeName, StringComparison.Ordinal));
} }
/// <inheritdoc />
public async Task RemovePlaylistsAsync(Guid userId)
{
var playlists = GetPlaylists(userId);
foreach (var playlist in playlists)
{
// Update owner if shared
var rankedShares = playlist.Shares.OrderByDescending(x => x.CanEdit).ToArray();
if (rankedShares.Length > 0 && Guid.TryParse(rankedShares[0].UserId, out var guid))
{
playlist.OwnerUserId = guid;
playlist.Shares = rankedShares.Skip(1).ToArray();
await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (playlist.IsFile)
{
SavePlaylistFile(playlist);
}
}
else
{
// Remove playlist if not shared
_libraryManager.DeleteItem(
playlist,
new DeleteOptions
{
DeleteFileLocation = false,
DeleteFromExternalProvider = false
},
playlist.GetParent(),
false);
}
}
}
/// <inheritdoc />
public async Task UpdatePlaylistAsync(Playlist playlist)
{
var currentPlaylist = (Playlist)_libraryManager.GetItemById(playlist.Id);
currentPlaylist.OwnerUserId = playlist.OwnerUserId;
currentPlaylist.Shares = playlist.Shares;
await playlist.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
if (currentPlaylist.IsFile)
{
SavePlaylistFile(currentPlaylist);
}
}
} }
} }

View File

@ -304,7 +304,7 @@ namespace Emby.Server.Implementations.Plugins
// If no version is given, return the current instance. // If no version is given, return the current instance.
var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList(); var plugins = _plugins.Where(p => p.Id.Equals(id)).ToList();
plugin = plugins.FirstOrDefault(p => p.Instance is not null) ?? plugins.OrderByDescending(p => p.Version).FirstOrDefault(); plugin = plugins.FirstOrDefault(p => p.Instance is not null) ?? plugins.MaxBy(p => p.Version);
} }
else else
{ {

View File

@ -100,7 +100,6 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
EnableImages = false EnableImages = false
}, },
SourceTypes = new SourceType[] { SourceType.Library }, SourceTypes = new SourceType[] { SourceType.Library },
HasChapterImages = false,
IsVirtualItem = false IsVirtualItem = false
}) })
.OfType<Video>() .OfType<Video>()

View File

@ -606,7 +606,7 @@ namespace Emby.Server.Implementations.Session
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogDebug("Error calling OnPlaybackStopped", ex); _logger.LogDebug(ex, "Error calling OnPlaybackStopped");
} }
} }
@ -953,7 +953,7 @@ namespace Emby.Server.Implementations.Session
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError("Error closing live stream", ex); _logger.LogError(ex, "Error closing live stream");
} }
} }

View File

@ -69,9 +69,7 @@ namespace Emby.Server.Implementations.Session
T data, T data,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
var socket = GetActiveSockets() var socket = GetActiveSockets().MaxBy(i => i.LastActivityDate);
.OrderByDescending(i => i.LastActivityDate)
.FirstOrDefault();
if (socket is null) if (socket is null)
{ {

View File

@ -620,10 +620,8 @@ namespace Emby.Server.Implementations.SyncPlay
RestartCurrentItem(); RestartCurrentItem();
return true; return true;
} }
else
{ return false;
return false;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -637,10 +635,8 @@ namespace Emby.Server.Implementations.SyncPlay
RestartCurrentItem(); RestartCurrentItem();
return true; return true;
} }
else
{ return false;
return false;
}
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -339,10 +339,8 @@ namespace Emby.Server.Implementations.SyncPlay
{ {
return sessionsCounter > 0; return sessionsCounter > 0;
} }
else
{ return false;
return false;
}
} }
/// <summary> /// <summary>

View File

@ -12,6 +12,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using Jellyfin.MediaEncoding.Hls.Playlist; using Jellyfin.MediaEncoding.Hls.Playlist;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -19,7 +20,6 @@ using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.MediaEncoding.Encoder; using MediaBrowser.MediaEncoding.Encoder;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
@ -1687,14 +1687,25 @@ public class DynamicHlsController : BaseJellyfinApiController
audioTranscodeParams += "-acodec " + audioCodec; audioTranscodeParams += "-acodec " + audioCodec;
if (state.OutputAudioBitrate.HasValue) var audioBitrate = state.OutputAudioBitrate;
var audioChannels = state.OutputAudioChannels;
if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
{ {
audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture); var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value / (audioChannels ?? 2));
if (_encodingOptions.EnableAudioVbr && vbrParam is not null)
{
audioTranscodeParams += vbrParam;
}
else
{
audioTranscodeParams += " -ab " + audioBitrate.Value.ToString(CultureInfo.InvariantCulture);
}
} }
if (state.OutputAudioChannels.HasValue) if (audioChannels.HasValue)
{ {
audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture); audioTranscodeParams += " -ac " + audioChannels.Value.ToString(CultureInfo.InvariantCulture);
} }
if (state.OutputAudioSampleRate.HasValue) if (state.OutputAudioSampleRate.HasValue)
@ -1708,11 +1719,11 @@ public class DynamicHlsController : BaseJellyfinApiController
// dts, flac, opus and truehd are experimental in mp4 muxer // dts, flac, opus and truehd are experimental in mp4 muxer
var strictArgs = string.Empty; var strictArgs = string.Empty;
var actualOutputAudioCodec = state.ActualOutputAudioCodec;
if (string.Equals(state.ActualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase) if (string.Equals(actualOutputAudioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase) || string.Equals(actualOutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase) || string.Equals(actualOutputAudioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase)) || string.Equals(actualOutputAudioCodec, "truehd", StringComparison.OrdinalIgnoreCase))
{ {
strictArgs = " -strict -2"; strictArgs = " -strict -2";
} }
@ -1746,10 +1757,17 @@ public class DynamicHlsController : BaseJellyfinApiController
} }
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase))
if (bitrate.HasValue)
{ {
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value / (channels ?? 2));
if (_encodingOptions.EnableAudioVbr && vbrParam is not null)
{
args += vbrParam;
}
else
{
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
} }
if (state.OutputAudioSampleRate.HasValue) if (state.OutputAudioSampleRate.HasValue)
@ -2011,8 +2029,7 @@ public class DynamicHlsController : BaseJellyfinApiController
{ {
return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false) return fileSystem.GetFiles(folder, new[] { segmentExtension }, true, false)
.Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase)) .Where(i => Path.GetFileNameWithoutExtension(i.Name).StartsWith(filePrefix, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(fileSystem.GetLastWriteTimeUtc) .MaxBy(fileSystem.GetLastWriteTimeUtc);
.FirstOrDefault();
} }
catch (IOException) catch (IOException)
{ {

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;

View File

@ -15,6 +15,7 @@ using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress; using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -332,12 +333,26 @@ public class LibraryController : BaseJellyfinApiController
[Authorize] [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteItem(Guid itemId) public ActionResult DeleteItem(Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var isApiKey = User.GetIsApiKey();
var user = _userManager.GetUserById(User.GetUserId()); var userId = User.GetUserId();
var user = !isApiKey && !userId.Equals(default)
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null;
if (!isApiKey && user is null)
{
return Unauthorized("Unauthorized access");
}
if (!item.CanDelete(user)) var item = _libraryManager.GetItemById(itemId);
if (item is null)
{
return NotFound();
}
if (user is not null && !item.CanDelete(user))
{ {
return Unauthorized("Unauthorized access"); return Unauthorized("Unauthorized access");
} }
@ -361,26 +376,31 @@ public class LibraryController : BaseJellyfinApiController
[Authorize] [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
{ {
if (ids.Length == 0) var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId();
var user = !isApiKey && !userId.Equals(default)
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
: null;
if (!isApiKey && user is null)
{ {
return NoContent(); return Unauthorized("Unauthorized access");
} }
foreach (var i in ids) foreach (var i in ids)
{ {
var item = _libraryManager.GetItemById(i); var item = _libraryManager.GetItemById(i);
var user = _userManager.GetUserById(User.GetUserId()); if (item is null)
if (!item.CanDelete(user))
{ {
if (ids.Length > 1) return NotFound();
{ }
return Unauthorized("Unauthorized access");
}
continue; if (user is not null && !item.CanDelete(user))
{
return Unauthorized("Unauthorized access");
} }
_libraryManager.DeleteItem( _libraryManager.DeleteItem(

View File

@ -146,7 +146,7 @@ public class PluginsController : BaseJellyfinApiController
var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)).ToList(); var plugins = _pluginManager.Plugins.Where(p => p.Id.Equals(pluginId)).ToList();
// Select the un-instanced one first. // Select the un-instanced one first.
var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.OrderBy(p => p.Manifest.Status).FirstOrDefault(); var plugin = plugins.FirstOrDefault(p => p.Instance is null) ?? plugins.MinBy(p => p.Manifest.Status);
if (plugin is not null) if (plugin is not null)
{ {

View File

@ -1,8 +1,6 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;

View File

@ -3,7 +3,6 @@ using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;

View File

@ -533,10 +533,8 @@ public class SubtitleController : BaseJellyfinApiController
_logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize); _logger.LogDebug("Fallback font size is {FileSize} Bytes", fileSize);
return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName)); return PhysicalFile(fontFile.FullName, MimeTypes.GetMimeType(fontFile.FullName));
} }
else
{ _logger.LogWarning("The selected font is null or empty");
_logger.LogWarning("The selected font is null or empty");
}
} }
else else
{ {

View File

@ -5,7 +5,6 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;

View File

@ -15,6 +15,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.QuickConnect; using MediaBrowser.Controller.QuickConnect;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
@ -41,6 +42,7 @@ public class UserController : BaseJellyfinApiController
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly IQuickConnect _quickConnectManager; private readonly IQuickConnect _quickConnectManager;
private readonly IPlaylistManager _playlistManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UserController"/> class. /// Initializes a new instance of the <see cref="UserController"/> class.
@ -53,6 +55,7 @@ public class UserController : BaseJellyfinApiController
/// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param> /// <param name="config">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param> /// <param name="quickConnectManager">Instance of the <see cref="IQuickConnect"/> interface.</param>
/// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
public UserController( public UserController(
IUserManager userManager, IUserManager userManager,
ISessionManager sessionManager, ISessionManager sessionManager,
@ -61,7 +64,8 @@ public class UserController : BaseJellyfinApiController
IAuthorizationContext authContext, IAuthorizationContext authContext,
IServerConfigurationManager config, IServerConfigurationManager config,
ILogger<UserController> logger, ILogger<UserController> logger,
IQuickConnect quickConnectManager) IQuickConnect quickConnectManager,
IPlaylistManager playlistManager)
{ {
_userManager = userManager; _userManager = userManager;
_sessionManager = sessionManager; _sessionManager = sessionManager;
@ -71,6 +75,7 @@ public class UserController : BaseJellyfinApiController
_config = config; _config = config;
_logger = logger; _logger = logger;
_quickConnectManager = quickConnectManager; _quickConnectManager = quickConnectManager;
_playlistManager = playlistManager;
} }
/// <summary> /// <summary>
@ -153,6 +158,7 @@ public class UserController : BaseJellyfinApiController
} }
await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false); await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
await _playlistManager.RemovePlaylistsAsync(userId).ConfigureAwait(false);
await _userManager.DeleteUserAsync(userId).ConfigureAwait(false); await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
return NoContent(); return NoContent();
} }

View File

@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
@ -223,9 +224,17 @@ public class DynamicHlsHelper
sdrVideoUrl += "&AllowVideoStreamCopy=false"; sdrVideoUrl += "&AllowVideoStreamCopy=false";
var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); var sdrOutputVideoBitrate = _encodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec);
var sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream) ?? 0; var sdrOutputAudioBitrate = 0;
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate; if (EncodingHelper.LosslessAudioCodecs.Contains(state.VideoRequest.AudioCodec, StringComparison.OrdinalIgnoreCase))
{
sdrOutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
sdrOutputAudioBitrate = _encodingHelper.GetAudioBitrateParam(state.VideoRequest, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
var sdrTotalBitrate = sdrOutputAudioBitrate + sdrOutputVideoBitrate;
var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup); var sdrPlaylist = AppendPlaylist(builder, state, sdrVideoUrl, sdrTotalBitrate, subtitleGroup);
// Provide a workaround for the case issue between flac and fLaC. // Provide a workaround for the case issue between flac and fLaC.

View File

@ -181,12 +181,18 @@ public static class StreamingHelpers
: GetOutputFileExtension(state, mediaSource); : GetOutputFileExtension(state, mediaSource);
} }
var outputAudioCodec = streamingRequest.AudioCodec;
if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec))
{
state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0;
}
else
{
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream, state.OutputAudioChannels) ?? 0;
}
state.OutputAudioCodec = outputAudioCodec;
state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.');
state.OutputAudioBitrate = encodingHelper.GetAudioBitrateParam(streamingRequest.AudioBitRate, streamingRequest.AudioCodec, state.AudioStream);
state.OutputAudioCodec = streamingRequest.AudioCodec;
state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec);
if (state.VideoRequest is not null) if (state.VideoRequest is not null)

View File

@ -0,0 +1,97 @@
namespace Jellyfin.Data.Enums;
/// <summary>
/// The person kind.
/// </summary>
public enum PersonKind
{
/// <summary>
/// An unknown person kind.
/// </summary>
Unknown,
/// <summary>
/// A person whose profession is acting on the stage, in films, or on television.
/// </summary>
Actor,
/// <summary>
/// A person who supervises the actors and other staff in a film, play, or similar production.
/// </summary>
Director,
/// <summary>
/// A person who writes music, especially as a professional occupation.
/// </summary>
Composer,
/// <summary>
/// A writer of a book, article, or document. Can also be used as a generic term for music writer if there is a lack of specificity.
/// </summary>
Writer,
/// <summary>
/// A well-known actor or other performer who appears in a work in which they do not have a regular role.
/// </summary>
GuestStar,
/// <summary>
/// A person responsible for the financial and managerial aspects of the making of a film or broadcast or for staging a play, opera, etc.
/// </summary>
Producer,
/// <summary>
/// A person who directs the performance of an orchestra or choir.
/// </summary>
Conductor,
/// <summary>
/// A person who writes the words to a song or musical.
/// </summary>
Lyricist,
/// <summary>
/// A person who adapts a musical composition for performance.
/// </summary>
Arranger,
/// <summary>
/// An audio engineer who performed a general engineering role.
/// </summary>
Engineer,
/// <summary>
/// An engineer responsible for using a mixing console to mix a recorded track into a single piece of music suitable for release.
/// </summary>
Mixer,
/// <summary>
/// A person who remixed a recording by taking one or more other tracks, substantially altering them and mixing them together with other material.
/// </summary>
Remixer,
/// <summary>
/// A person who created the material.
/// </summary>
Creator,
/// <summary>
/// A person who was the artist.
/// </summary>
Artist,
/// <summary>
/// A person who was the album artist.
/// </summary>
AlbumArtist,
/// <summary>
/// A person who was the author.
/// </summary>
Author,
/// <summary>
/// A person who was the illustrator.
/// </summary>
Illustrator,
}

View File

@ -4,7 +4,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
using EFCoreSecondLevelCacheInterceptor;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;

View File

@ -22,7 +22,6 @@ using MediaBrowser.Controller.Lyrics;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -40,7 +40,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.ReaddDefaultPluginRepository), typeof(Routines.ReaddDefaultPluginRepository),
typeof(Routines.MigrateDisplayPreferencesDb), typeof(Routines.MigrateDisplayPreferencesDb),
typeof(Routines.RemoveDownloadImagesInAdvance), typeof(Routines.RemoveDownloadImagesInAdvance),
typeof(Routines.MigrateAuthenticationDb) typeof(Routines.MigrateAuthenticationDb),
typeof(Routines.FixPlaylistOwner)
}; };
/// <summary> /// <summary>

View File

@ -44,9 +44,7 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine
return; return;
} }
var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration")); var oldPluginConfiguration = ReadOld(path);
using var xmlReader = XmlReader.Create(path);
var oldPluginConfiguration = serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
if (oldPluginConfiguration is not null) if (oldPluginConfiguration is not null)
{ {
@ -55,10 +53,25 @@ public class MigrateMusicBrainzTimeout : IMigrationRoutine
newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName; newPluginConfiguration.ReplaceArtistName = oldPluginConfiguration.ReplaceArtistName;
var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0; var newRateLimit = oldPluginConfiguration.RateLimit / 1000.0;
newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit; newPluginConfiguration.RateLimit = newRateLimit < 1.0 ? 1.0 : newRateLimit;
WriteNew(path, newPluginConfiguration);
}
}
var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration")); private OldMusicBrainzConfiguration? ReadOld(string path)
var xmlWriterSettings = new XmlWriterSettings { Indent = true }; {
using var xmlWriter = XmlWriter.Create(path, xmlWriterSettings); using (var xmlReader = XmlReader.Create(path))
{
var serverConfigSerializer = new XmlSerializer(typeof(OldMusicBrainzConfiguration), new XmlRootAttribute("PluginConfiguration"));
return serverConfigSerializer.Deserialize(xmlReader) as OldMusicBrainzConfiguration;
}
}
private void WriteNew(string path, PluginConfiguration newPluginConfiguration)
{
var pluginConfigurationSerializer = new XmlSerializer(typeof(PluginConfiguration), new XmlRootAttribute("PluginConfiguration"));
var xmlWriterSettings = new XmlWriterSettings { Indent = true };
using (var xmlWriter = XmlWriter.Create(path, xmlWriterSettings))
{
pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration); pluginConfigurationSerializer.Serialize(xmlWriter, newPluginConfiguration);
} }
} }

View File

@ -0,0 +1,67 @@
using System;
using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Migrations.Routines;
/// <summary>
/// Properly set playlist owner.
/// </summary>
internal class FixPlaylistOwner : IMigrationRoutine
{
private readonly ILogger<RemoveDuplicateExtras> _logger;
private readonly ILibraryManager _libraryManager;
private readonly IPlaylistManager _playlistManager;
public FixPlaylistOwner(
ILogger<RemoveDuplicateExtras> logger,
ILibraryManager libraryManager,
IPlaylistManager playlistManager)
{
_logger = logger;
_libraryManager = libraryManager;
_playlistManager = playlistManager;
}
/// <inheritdoc/>
public Guid Id => Guid.Parse("{615DFA9E-2497-4DBB-A472-61938B752C5B}");
/// <inheritdoc/>
public string Name => "FixPlaylistOwner";
/// <inheritdoc/>
public bool PerformOnNewInstall => false;
/// <inheritdoc/>
public void Perform()
{
var playlists = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { BaseItemKind.Playlist }
})
.Cast<Playlist>()
.Where(x => x.OwnerUserId.Equals(Guid.Empty))
.ToArray();
if (playlists.Length > 0)
{
foreach (var playlist in playlists)
{
var shares = playlist.Shares;
var firstEditShare = shares.First(x => x.CanEdit);
if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid))
{
playlist.OwnerUserId = guid;
playlist.Shares = shares.Where(x => x != firstEditShare).ToArray();
_playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult();
}
}
}
}
}

View File

@ -133,9 +133,7 @@ namespace Jellyfin.Server.Migrations.Routines
SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && int.TryParse(length, out var skipBackwardLength) SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && int.TryParse(length, out var skipBackwardLength)
? skipBackwardLength ? skipBackwardLength
: 10000, : 10000,
EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled) EnableNextVideoInfoOverlay = !dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) || string.IsNullOrEmpty(enabled) || bool.Parse(enabled),
? bool.Parse(enabled)
: true,
DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty, DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty,
TvHome = dto.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty TvHome = dto.CustomPrefs.TryGetValue("tvhome", out var home) ? home : string.Empty
}; };

View File

@ -4,7 +4,6 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Mime; using System.Net.Mime;
using System.Runtime.InteropServices;
using System.Text; using System.Text;
using Jellyfin.Api.Middleware; using Jellyfin.Api.Middleware;
using Jellyfin.MediaEncoding.Hls.Extensions; using Jellyfin.MediaEncoding.Hls.Extensions;

View File

@ -1,3 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Emby/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Jellyfin/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Jellyfin/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Playstate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Playstate/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -50,7 +50,7 @@ namespace MediaBrowser.Common.Plugins
if (Version is not null && !Directory.Exists(dataFolderPath)) if (Version is not null && !Directory.Exists(dataFolderPath))
{ {
// Try again with the version number appended to the folder name. // Try again with the version number appended to the folder name.
dataFolderPath += "_" + Version.ToString(); dataFolderPath += "_" + Version;
} }
SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version); SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);

View File

@ -801,16 +801,14 @@ namespace MediaBrowser.Controller.Entities
{ {
return allowed.Contains(ChannelId); return allowed.Contains(ChannelId);
} }
else
{
var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
foreach (var folder in collectionFolders) var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
foreach (var folder in collectionFolders)
{
if (allowed.Contains(folder.Id))
{ {
if (allowed.Contains(folder.Id)) return true;
{
return true;
}
} }
} }
@ -893,16 +891,6 @@ namespace MediaBrowser.Controller.Entities
var sortable = Name.Trim().ToLowerInvariant(); var sortable = Name.Trim().ToLowerInvariant();
foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
{
sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
}
foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
{
sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
}
foreach (var search in ConfigurationManager.Configuration.SortRemoveWords) foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
{ {
// Remove from beginning if a space follows // Remove from beginning if a space follows
@ -921,12 +909,22 @@ namespace MediaBrowser.Controller.Entities
} }
} }
foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
{
sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
}
foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
{
sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
}
return ModifySortChunks(sortable); return ModifySortChunks(sortable);
} }
internal static string ModifySortChunks(string name) internal static string ModifySortChunks(ReadOnlySpan<char> name)
{ {
void AppendChunk(StringBuilder builder, bool isDigitChunk, ReadOnlySpan<char> chunk) static void AppendChunk(StringBuilder builder, bool isDigitChunk, ReadOnlySpan<char> chunk)
{ {
if (isDigitChunk && chunk.Length < 10) if (isDigitChunk && chunk.Length < 10)
{ {
@ -936,7 +934,7 @@ namespace MediaBrowser.Controller.Entities
builder.Append(chunk); builder.Append(chunk);
} }
if (name.Length == 0) if (name.IsEmpty)
{ {
return string.Empty; return string.Empty;
} }
@ -950,13 +948,13 @@ namespace MediaBrowser.Controller.Entities
var isDigit = char.IsDigit(name[i]); var isDigit = char.IsDigit(name[i]);
if (isDigit != isDigitChunk) if (isDigit != isDigitChunk)
{ {
AppendChunk(builder, isDigitChunk, name.AsSpan(chunkStart, i - chunkStart)); AppendChunk(builder, isDigitChunk, name.Slice(chunkStart, i - chunkStart));
chunkStart = i; chunkStart = i;
isDigitChunk = isDigit; isDigitChunk = isDigit;
} }
} }
AppendChunk(builder, isDigitChunk, name.AsSpan(chunkStart)); AppendChunk(builder, isDigitChunk, name.Slice(chunkStart));
// logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString()); // logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
return builder.ToString().RemoveDiacritics(); return builder.ToString().RemoveDiacritics();

View File

@ -1,11 +0,0 @@
#nullable disable
#pragma warning disable CA1819, CS1591
namespace MediaBrowser.Controller.Entities
{
public interface IHasShares
{
Share[] Shares { get; set; }
}
}

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -17,38 +18,38 @@ namespace MediaBrowser.Controller.Entities
// Normalize // Normalize
if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
{ {
person.Type = PersonType.GuestStar; person.Type = PersonKind.GuestStar;
} }
else if (string.Equals(person.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(person.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
{ {
person.Type = PersonType.Director; person.Type = PersonKind.Director;
} }
else if (string.Equals(person.Role, PersonType.Producer, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(person.Role, PersonType.Producer, StringComparison.OrdinalIgnoreCase))
{ {
person.Type = PersonType.Producer; person.Type = PersonKind.Producer;
} }
else if (string.Equals(person.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase)) else if (string.Equals(person.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
{ {
person.Type = PersonType.Writer; person.Type = PersonKind.Writer;
} }
// If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes // If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
if (string.Equals(person.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)) if (person.Type == PersonKind.GuestStar)
{ {
var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase)); var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type == PersonKind.Actor);
if (existing is not null) if (existing is not null)
{ {
existing.Type = PersonType.GuestStar; existing.Type = PersonKind.GuestStar;
MergeExisting(existing, person); MergeExisting(existing, person);
return; return;
} }
} }
if (string.Equals(person.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase)) if (person.Type == PersonKind.Actor)
{ {
// If the actor already exists without a role and we have one, fill it in // If the actor already exists without a role and we have one, fill it in
var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase) || p.Type.Equals(PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))); var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type == PersonKind.Actor || p.Type == PersonKind.GuestStar));
if (existing is null) if (existing is null)
{ {
// Wasn't there - add it // Wasn't there - add it
@ -68,8 +69,8 @@ namespace MediaBrowser.Controller.Entities
else else
{ {
var existing = people.FirstOrDefault(p => var existing = people.FirstOrDefault(p =>
string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase)
string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase)); && p.Type == person.Type);
// Check for dupes based on the combination of Name and Type // Check for dupes based on the combination of Name and Type
if (existing is null) if (existing is null)

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
@ -36,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the type. /// Gets or sets the type.
/// </summary> /// </summary>
/// <value>The type.</value> /// <value>The type.</value>
public string Type { get; set; } public PersonKind Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the ascending sort order. /// Gets or sets the ascending sort order.
@ -57,10 +58,6 @@ namespace MediaBrowser.Controller.Entities
return Name; return Name;
} }
public bool IsType(string type) public bool IsType(PersonKind type) => Type == type || string.Equals(type.ToString(), Role, StringComparison.OrdinalIgnoreCase);
{
return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase)
|| string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
}
} }
} }

View File

@ -1,13 +0,0 @@
#nullable disable
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Entities
{
public class Share
{
public string UserId { get; set; }
public bool CanEdit { get; set; }
}
}

View File

@ -197,10 +197,8 @@ namespace MediaBrowser.Controller.LiveTv
{ {
return 2.0 / 3; return 2.0 / 3;
} }
else
{ return 16.0 / 9;
return 16.0 / 9;
}
} }
public override string GetClientTypeName() public override string GetClientTypeName()

View File

@ -1,5 +1,3 @@
using System;
namespace MediaBrowser.Controller.Lyrics; namespace MediaBrowser.Controller.Lyrics;
/// <summary> /// <summary>

View File

@ -89,6 +89,16 @@ namespace MediaBrowser.Controller.MediaEncoding
{ "truehd", 6 }, { "truehd", 6 },
}; };
public static readonly string[] LosslessAudioCodecs = new string[]
{
"alac",
"ape",
"flac",
"mlp",
"truehd",
"wavpack"
};
public EncodingHelper( public EncodingHelper(
IApplicationPaths appPaths, IApplicationPaths appPaths,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
@ -128,14 +138,10 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType) if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding && encodingOptions.EnableHardwareEncoding
&& codecMap.ContainsKey(hwType)) && codecMap.TryGetValue(hwType, out var preferredEncoder)
&& _mediaEncoder.SupportsEncoder(preferredEncoder))
{ {
var preferredEncoder = codecMap[hwType]; return preferredEncoder;
if (_mediaEncoder.SupportsEncoder(preferredEncoder))
{
return preferredEncoder;
}
} }
} }
@ -551,7 +557,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
} }
else if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
{ {
return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase)); return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
} }
@ -620,6 +627,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return "flac"; return "flac";
} }
if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
{
return "dca";
}
return codec.ToLowerInvariant(); return codec.ToLowerInvariant();
} }
@ -827,39 +839,38 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias); var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
if (isHwTonemapAvailable && IsOpenclFullSupported()) if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
{ {
if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965) if (doOclTonemap && !isVaapiDecoder)
{ {
if (!isVaapiDecoder) args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
{
args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
}
else if (_mediaEncoder.IsVaapiDeviceAmd)
{
if (!IsVulkanFullSupported()
|| !_mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
|| Environment.OSVersion.Version < _minKernelVersionAmdVkFmtModifier)
{
args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
else
{
// libplacebo wants an explicitly set vulkan filter device.
args.Append(GetVulkanDeviceArgs(0, null, VaapiAlias, VulkanAlias));
filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
}
}
else
{
args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias); filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
} }
} }
else if (_mediaEncoder.IsVaapiDeviceAmd)
{
if (IsVulkanFullSupported()
&& _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
&& Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
{
// libplacebo wants an explicitly set vulkan filter device.
args.Append(GetVulkanDeviceArgs(0, null, VaapiAlias, VulkanAlias));
filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
}
else if (doOclTonemap)
{
// ROCm/ROCr OpenCL runtime
args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
}
else if (doOclTonemap)
{
args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
args.Append(filterDevArgs); args.Append(filterDevArgs);
} }
@ -1099,19 +1110,19 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return "-bsf:v h264_mp4toannexb"; return "-bsf:v h264_mp4toannexb";
} }
else if (IsH265(stream))
if (IsH265(stream))
{ {
return "-bsf:v hevc_mp4toannexb"; return "-bsf:v hevc_mp4toannexb";
} }
else if (IsAAC(stream))
if (IsAAC(stream))
{ {
// Convert adts header(mpegts) to asc header(mp4). // Convert adts header(mpegts) to asc header(mp4).
return "-bsf:a aac_adtstoasc"; return "-bsf:a aac_adtstoasc";
} }
else
{ return null;
return null;
}
} }
public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer) public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
@ -1189,10 +1200,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
} }
else
{ return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
} }
return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
@ -1406,7 +1415,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var param = string.Empty; var param = string.Empty;
// Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding. // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
// https://01.org/linuxgraphics/downloads/firmware // https://01.org/group/43/downloads/firmware
// https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading // https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
// Intel Low Power Encoding can save unnecessary CPU-GPU synchronization, // Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping. // which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
@ -2050,9 +2059,9 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
} }
// Video bitrate must fall within requested value // Audio bitrate must fall within requested value
if (request.AudioBitRate.HasValue if (request.AudioBitRate.HasValue
&& audioStream.BitDepth.HasValue && audioStream.BitRate.HasValue
&& audioStream.BitRate.Value > request.AudioBitRate.Value) && audioStream.BitRate.Value > request.AudioBitRate.Value)
{ {
return false; return false;
@ -2139,7 +2148,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec); var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
// Don't scale the real bitrate lower than the requested bitrate // Don't scale the real bitrate lower than the requested bitrate
var scaleFactor = Math.Min(outputScaleFactor / inputScaleFactor, 1); var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
if (bitrate <= 500000) if (bitrate <= 500000)
{ {
@ -2161,56 +2170,96 @@ namespace MediaBrowser.Controller.MediaEncoding
return Convert.ToInt32(scaleFactor * bitrate); return Convert.ToInt32(scaleFactor * bitrate);
} }
public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream) public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels)
{ {
return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream); return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
} }
public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream) public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels)
{ {
if (audioStream is null) if (audioStream is null)
{ {
return null; return null;
} }
if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec)) var inputChannels = audioStream.Channels ?? 0;
var outputChannels = outputAudioChannels ?? 0;
var bitrate = audioBitRate ?? int.MaxValue;
if (string.IsNullOrEmpty(audioCodec)
|| string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{ {
return Math.Min(384000, audioBitRate.Value); return (inputChannels, outputChannels) switch
{
(>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
(> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
(> 0, _) => Math.Min(inputChannels * 128000, bitrate),
(_, _) => Math.Min(384000, bitrate)
};
} }
if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec)) if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
{ {
if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase) return (inputChannels, outputChannels) switch
|| string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{ {
if ((audioStream.Channels ?? 0) >= 6) (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
{ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
return Math.Min(640000, audioBitRate.Value); (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
} (_, _) => Math.Min(672000, bitrate)
};
return Math.Min(384000, audioBitRate.Value);
}
if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
|| string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
{
if ((audioStream.Channels ?? 0) >= 6)
{
return Math.Min(3584000, audioBitRate.Value);
}
return Math.Min(1536000, audioBitRate.Value);
}
} }
// Empty bitrate area is not allow on iOS // Empty bitrate area is not allow on iOS
// Default audio bitrate to 128K if it is not being requested // Default audio bitrate to 128K per channel if we don't have codec specific defaults
// https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
return 128000; return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
}
public string GetAudioVbrModeParam(string encoder, int bitratePerChannel)
{
if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
{
return " -vbr:a " + bitratePerChannel switch
{
< 32000 => "1",
< 48000 => "2",
< 64000 => "3",
< 96000 => "4",
_ => "5"
};
}
if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
{
return " -qscale:a " + bitratePerChannel switch
{
< 48000 => "8",
< 64000 => "6",
< 88000 => "4",
< 112000 => "2",
_ => "0"
};
}
if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
{
return " -qscale:a " + bitratePerChannel switch
{
< 40000 => "0",
< 56000 => "2",
< 80000 => "4",
< 112000 => "6",
_ => "8"
};
}
return null;
} }
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions) public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
@ -2564,8 +2613,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (outputWidth > maximumWidth || outputHeight > maximumHeight) if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{ {
var scaleW = (double)maximumWidth / (double)outputWidth; var scaleW = (double)maximumWidth / outputWidth;
var scaleH = (double)maximumHeight / (double)outputHeight; var scaleH = (double)maximumHeight / outputHeight;
var scale = Math.Min(scaleW, scaleH); var scale = Math.Min(scaleW, scaleH);
outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale)); outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale));
outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale)); outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale));
@ -2712,79 +2761,76 @@ namespace MediaBrowser.Controller.MediaEncoding
widthParam, widthParam,
heightParam); heightParam);
} }
else
{ return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
}
} }
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size // If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
{ {
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2", "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2",
maxWidthParam, maxWidthParam,
maxHeightParam, maxHeightParam,
scaleVal); scaleVal);
} }
// If a fixed width was requested // If a fixed width was requested
else if (requestedWidth.HasValue) if (requestedWidth.HasValue)
{ {
if (threedFormat.HasValue) if (threedFormat.HasValue)
{ {
// This method can handle 0 being passed in for the requested height // This method can handle 0 being passed in for the requested height
return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0); return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
} }
else
{
var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
return string.Format( var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
CultureInfo.InvariantCulture,
"scale={0}:trunc(ow/a/2)*2", return string.Format(
widthParam); CultureInfo.InvariantCulture,
} "scale={0}:trunc(ow/a/2)*2",
widthParam);
} }
// If a fixed height was requested // If a fixed height was requested
else if (requestedHeight.HasValue) if (requestedHeight.HasValue)
{ {
var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture); var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:{0}", "scale=trunc(oh*a/{1})*{1}:{0}",
heightParam, heightParam,
scaleVal); scaleVal);
} }
// If a max width was requested // If a max width was requested
else if (requestedMaxWidth.HasValue) if (requestedMaxWidth.HasValue)
{ {
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture); var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2", "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
maxWidthParam, maxWidthParam,
scaleVal); scaleVal);
} }
// If a max height was requested // If a max height was requested
else if (requestedMaxHeight.HasValue) if (requestedMaxHeight.HasValue)
{ {
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture); var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})", "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
maxHeightParam, maxHeightParam,
scaleVal); scaleVal);
} }
return string.Empty; return string.Empty;
@ -2858,18 +2904,21 @@ namespace MediaBrowser.Controller.MediaEncoding
"yadif_cuda={0}:-1:0", "yadif_cuda={0}:-1:0",
doubleRateDeint ? "1" : "0"); doubleRateDeint ? "1" : "0");
} }
else if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{ {
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"deinterlace_vaapi=rate={0}", "deinterlace_vaapi=rate={0}",
doubleRateDeint ? "field" : "frame"); doubleRateDeint ? "field" : "frame");
} }
else if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{ {
return "deinterlace_qsv=mode=2"; return "deinterlace_qsv=mode=2";
} }
else if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
{ {
return string.Format( return string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@ -2900,7 +2949,8 @@ namespace MediaBrowser.Controller.MediaEncoding
options.VppTonemappingBrightness, options.VppTonemappingBrightness,
options.VppTonemappingContrast); options.VppTonemappingContrast);
} }
else if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
{ {
args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none"; args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none";
@ -4214,7 +4264,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// sw => hw // sw => hw
if (doVkTonemap) if (doVkTonemap)
{ {
mainFilters.Add("hwupload_vaapi"); mainFilters.Add("hwupload=derive_device=vaapi");
mainFilters.Add("format=vaapi");
mainFilters.Add("hwmap=derive_device=vulkan"); mainFilters.Add("hwmap=derive_device=vulkan");
mainFilters.Add("format=vulkan"); mainFilters.Add("format=vulkan");
} }
@ -4325,12 +4376,15 @@ namespace MediaBrowser.Controller.MediaEncoding
// prefer vaapi hwupload to vulkan hwupload, // prefer vaapi hwupload to vulkan hwupload,
// Mesa RADV does not support a dedicated transfer queue. // Mesa RADV does not support a dedicated transfer queue.
subFilters.Add("hwupload_vaapi"); subFilters.Add("hwupload=derive_device=vaapi");
subFilters.Add("format=vaapi");
subFilters.Add("hwmap=derive_device=vulkan"); subFilters.Add("hwmap=derive_device=vulkan");
subFilters.Add("format=vulkan"); subFilters.Add("format=vulkan");
overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0"); overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
overlayFilters.Add("scale_vulkan=format=nv12");
// TODO: figure out why libplacebo can sync without vaSyncSurface VPP support in radeonsi.
overlayFilters.Add("libplacebo=format=nv12:apply_filmgrain=0:apply_dolbyvision=0:upscaler=none:downscaler=none:dithering=none");
// OUTPUT vaapi(nv12/bgra) surface(vram) // OUTPUT vaapi(nv12/bgra) surface(vram)
// reverse-mapping via vaapi-vulkan interop. // reverse-mapping via vaapi-vulkan interop.
@ -4772,26 +4826,27 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
return videoStream.BitDepth.Value; return videoStream.BitDepth.Value;
} }
else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase) if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase)) || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{ {
return 8; return 8;
} }
else if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
{ {
return 10; return 10;
} }
else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase)) if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
{ {
return 12; return 12;
} }
else
{ return 8;
return 8;
}
} }
return 0; return 0;
@ -5023,11 +5078,9 @@ namespace MediaBrowser.Controller.MediaEncoding
return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty)
+ (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty); + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
} }
else
{ // cuvid decoder doesn't have threading issue.
// cuvid decoder doesn't have threading issue. return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
}
} }
} }
@ -5385,7 +5438,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Automatically set thread count // Automatically set thread count
return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0; return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
} }
else if (threads >= Environment.ProcessorCount)
if (threads >= Environment.ProcessorCount)
{ {
return Environment.ProcessorCount; return Environment.ProcessorCount;
} }
@ -5670,14 +5724,22 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6; var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
var shiftAudioCodecs = new List<string>();
if (inputChannels >= 6) if (inputChannels >= 6)
{ {
return; // DTS and TrueHD are not supported by HLS
// Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
shiftAudioCodecs.Add("dca");
shiftAudioCodecs.Add("truehd");
}
else
{
// Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
// Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
shiftAudioCodecs.Add("ac3");
shiftAudioCodecs.Add("eac3");
} }
// Transcoding to 2ch ac3 almost always causes a playback failure
// Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
var shiftAudioCodecs = new[] { "ac3", "eac3" };
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase))) if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{ {
return; return;
@ -5919,10 +5981,17 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
if (bitrate.HasValue)
{ {
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture); var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2));
if (encodingOptions.EnableAudioVbr && vbrParam is not null)
{
args += vbrParam;
}
else
{
args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
} }
if (state.OutputAudioSampleRate.HasValue) if (state.OutputAudioSampleRate.HasValue)
@ -5940,23 +6009,33 @@ namespace MediaBrowser.Controller.MediaEncoding
var audioTranscodeParams = new List<string>(); var audioTranscodeParams = new List<string>();
var bitrate = state.OutputAudioBitrate; var bitrate = state.OutputAudioBitrate;
var channels = state.OutputAudioChannels;
var outputCodec = state.OutputAudioCodec;
if (bitrate.HasValue) if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
{ {
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture)); var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2));
if (encodingOptions.EnableAudioVbr && vbrParam is not null)
{
audioTranscodeParams.Add(vbrParam);
}
else
{
audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
}
} }
if (state.OutputAudioChannels.HasValue) if (channels.HasValue)
{ {
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture)); audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
} }
if (!string.IsNullOrEmpty(state.OutputAudioCodec)) if (!string.IsNullOrEmpty(outputCodec))
{ {
audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state)); audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
} }
if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase)) if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{ {
// opus only supports specific sampling rates // opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate; var sampleRate = state.OutputAudioSampleRate;

View File

@ -56,5 +56,20 @@ namespace MediaBrowser.Controller.Playlists
/// <param name="newIndex">The new index.</param> /// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns> /// <returns>Task.</returns>
Task MoveItemAsync(string playlistId, string entryId, int newIndex); Task MoveItemAsync(string playlistId, string entryId, int newIndex);
/// <summary>
/// Removed all playlists of a user.
/// If the playlist is shared, ownership is transferred.
/// </summary>
/// <param name="userId">The user id.</param>
/// <returns>Task.</returns>
Task RemovePlaylistsAsync(Guid userId);
/// <summary>
/// Updates a playlist.
/// </summary>
/// <param name="playlist">The updated playlist.</param>
/// <returns>Task.</returns>
Task UpdatePlaylistAsync(Playlist playlist);
} }
} }

View File

@ -15,6 +15,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Playlists namespace MediaBrowser.Controller.Playlists
@ -232,7 +233,8 @@ namespace MediaBrowser.Controller.Playlists
return base.IsVisible(user); return base.IsVisible(user);
} }
if (user.Id.Equals(OwnerUserId)) var userId = user.Id;
if (userId.Equals(OwnerUserId))
{ {
return true; return true;
} }
@ -240,10 +242,9 @@ namespace MediaBrowser.Controller.Playlists
var shares = Shares; var shares = Shares;
if (shares.Length == 0) if (shares.Length == 0)
{ {
return base.IsVisible(user); return false;
} }
var userId = user.Id;
return shares.Any(share => Guid.TryParse(share.UserId, out var id) && id.Equals(userId)); return shares.Any(share => Guid.TryParse(share.UserId, out var id) && id.Equals(userId));
} }

View File

@ -1,6 +1,7 @@
#pragma warning disable CA1819, CS1591 #pragma warning disable CA1819, CS1591
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.Providers
public bool ReplaceAllImages { get; set; } public bool ReplaceAllImages { get; set; }
public ImageType[] ReplaceImages { get; set; } public IReadOnlyList<ImageType> ReplaceImages { get; set; }
public bool IsAutomated { get; set; } public bool IsAutomated { get; set; }

View File

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Entities.Security; using Jellyfin.Data.Entities.Security;
using Jellyfin.Data.Events;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Session; using MediaBrowser.Model.Session;

View File

@ -1,7 +1,6 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;

View File

@ -533,11 +533,9 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
_logger.LogWarning("Session {SessionId} is seeking to wrong position, correcting.", session.Id); _logger.LogWarning("Session {SessionId} is seeking to wrong position, correcting.", session.Id);
return; return;
} }
else
{ // Session is ready.
// Session is ready. context.SetBuffering(session, false);
context.SetBuffering(session, false);
}
if (!context.IsBuffering()) if (!context.IsBuffering())
{ {

View File

@ -313,17 +313,13 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
return true; return true;
} }
else
{ // Restoring playing item.
// Restoring playing item. SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
return false;
}
}
else
{
return false; return false;
} }
return false;
} }
/// <summary> /// <summary>
@ -528,10 +524,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{ {
return _shuffledPlaylist; return _shuffledPlaylist;
} }
else
{ return _sortedPlaylist;
return _sortedPlaylist;
}
} }
/// <summary> /// <summary>
@ -544,14 +538,13 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{ {
return null; return null;
} }
else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{ {
return _shuffledPlaylist[PlayingItemIndex]; return _shuffledPlaylist[PlayingItemIndex];
} }
else
{ return _sortedPlaylist[PlayingItemIndex];
return _sortedPlaylist[PlayingItemIndex];
}
} }
} }
} }

View File

@ -6,8 +6,10 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Xml; using System.Xml;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -71,10 +73,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
foreach (var info in idInfos) foreach (var info in idInfos)
{ {
var id = info.Key + "Id"; var id = info.Key + "Id";
if (!_validProviderIds.ContainsKey(id)) _validProviderIds.TryAdd(id, info.Key);
{
_validProviderIds.Add(id, info.Key);
}
} }
// Additional Mappings // Additional Mappings
@ -370,7 +369,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
case "Director": case "Director":
{ {
foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Director })) foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonKind.Director }))
{ {
if (string.IsNullOrWhiteSpace(p.Name)) if (string.IsNullOrWhiteSpace(p.Name))
{ {
@ -385,7 +384,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
case "Writer": case "Writer":
{ {
foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Writer })) foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonKind.Writer }))
{ {
if (string.IsNullOrWhiteSpace(p.Name)) if (string.IsNullOrWhiteSpace(p.Name))
{ {
@ -412,7 +411,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
else else
{ {
// Old-style piped string // Old-style piped string
foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.Actor })) foreach (var p in SplitNames(actors).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonKind.Actor }))
{ {
if (string.IsNullOrWhiteSpace(p.Name)) if (string.IsNullOrWhiteSpace(p.Name))
{ {
@ -428,7 +427,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
case "GuestStars": case "GuestStars":
{ {
foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonType.GuestStar })) foreach (var p in SplitNames(reader.ReadElementContentAsString()).Select(v => new PersonInfo { Name = v.Trim(), Type = PersonKind.GuestStar }))
{ {
if (string.IsNullOrWhiteSpace(p.Name)) if (string.IsNullOrWhiteSpace(p.Name))
{ {
@ -636,6 +635,21 @@ namespace MediaBrowser.LocalMetadata.Parsers
break; break;
} }
case "OwnerUserId":
{
var val = reader.ReadElementContentAsString();
if (Guid.TryParse(val, out var guid) && !guid.Equals(Guid.Empty))
{
if (item is Playlist playlist)
{
playlist.OwnerUserId = guid;
}
}
break;
}
case "Format3D": case "Format3D":
{ {
var val = reader.ReadElementContentAsString(); var val = reader.ReadElementContentAsString();
@ -1035,7 +1049,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader) private IEnumerable<PersonInfo> GetPersonsFromXmlNode(XmlReader reader)
{ {
var name = string.Empty; var name = string.Empty;
var type = PersonType.Actor; // If type is not specified assume actor var type = PersonKind.Actor; // If type is not specified assume actor
var role = string.Empty; var role = string.Empty;
int? sortOrder = null; int? sortOrder = null;
@ -1056,11 +1070,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
case "Type": case "Type":
{ {
var val = reader.ReadElementContentAsString(); var val = reader.ReadElementContentAsString();
_ = Enum.TryParse(val, true, out type);
if (!string.IsNullOrWhiteSpace(val))
{
type = val;
}
break; break;
} }

View File

@ -374,7 +374,7 @@ namespace MediaBrowser.LocalMetadata.Savers
{ {
await writer.WriteStartElementAsync(null, "Person", null).ConfigureAwait(false); await writer.WriteStartElementAsync(null, "Person", null).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "Name", null, person.Name).ConfigureAwait(false); await writer.WriteElementStringAsync(null, "Name", null, person.Name).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "Type", null, person.Type).ConfigureAwait(false); await writer.WriteElementStringAsync(null, "Type", null, person.Type.ToString()).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "Role", null, person.Role).ConfigureAwait(false); await writer.WriteElementStringAsync(null, "Role", null, person.Role).ConfigureAwait(false);
if (person.SortOrder.HasValue) if (person.SortOrder.HasValue)
@ -395,6 +395,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (item is Playlist playlist && !Playlist.IsPlaylistFile(playlist.Path)) if (item is Playlist playlist && !Playlist.IsPlaylistFile(playlist.Path))
{ {
await writer.WriteElementStringAsync(null, "OwnerUserId", null, playlist.OwnerUserId.ToString("N")).ConfigureAwait(false);
await AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem").ConfigureAwait(false); await AddLinkedChildren(playlist, writer, "PlaylistItems", "PlaylistItem").ConfigureAwait(false);
} }
@ -418,16 +419,19 @@ namespace MediaBrowser.LocalMetadata.Savers
foreach (var share in item.Shares) foreach (var share in item.Shares)
{ {
await writer.WriteStartElementAsync(null, "Share", null).ConfigureAwait(false); if (share.UserId is not null)
{
await writer.WriteStartElementAsync(null, "Share", null).ConfigureAwait(false);
await writer.WriteElementStringAsync(null, "UserId", null, share.UserId).ConfigureAwait(false); await writer.WriteElementStringAsync(null, "UserId", null, share.UserId).ConfigureAwait(false);
await writer.WriteElementStringAsync( await writer.WriteElementStringAsync(
null, null,
"CanEdit", "CanEdit",
null, null,
share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false); share.CanEdit.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()).ConfigureAwait(false);
await writer.WriteEndElementAsync().ConfigureAwait(false); await writer.WriteEndElementAsync().ConfigureAwait(false);
}
} }
await writer.WriteEndElementAsync().ConfigureAwait(false); await writer.WriteEndElementAsync().ConfigureAwait(false);

View File

@ -231,10 +231,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
throw new InvalidOperationException( throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
} }
else
{ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
_logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
}
} }
private async Task<Stream> GetAttachmentStream( private async Task<Stream> GetAttachmentStream(
@ -376,10 +374,8 @@ namespace MediaBrowser.MediaEncoding.Attachments
throw new InvalidOperationException( throw new InvalidOperationException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath)); string.Format(CultureInfo.InvariantCulture, "ffmpeg attachment extraction failed for {0} to {1}", inputPath, outputPath));
} }
else
{ _logger.LogInformation("ffmpeg attachment extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
_logger.LogInformation("ffmpeg attachment extraction completed for {Path} to {Path}", inputPath, outputPath);
}
} }
private string GetAttachmentCachePath(string mediaPath, MediaSourceInfo mediaSource, int attachmentStreamIndex) private string GetAttachmentCachePath(string mediaPath, MediaSourceInfo mediaSource, int attachmentStreamIndex)

View File

@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using BDInfo.IO; using BDInfo.IO;
@ -105,7 +104,7 @@ public class BdInfoDirectoryInfo : IDirectoryInfo
_impl.FullName, _impl.FullName,
new[] { searchPattern }, new[] { searchPattern },
false, false,
(searchOption & SearchOption.AllDirectories) == SearchOption.AllDirectories) searchOption == SearchOption.AllDirectories)
.Select(x => new BdInfoFileInfo(x)) .Select(x => new BdInfoFileInfo(x))
.ToArray(); .ToArray();
} }

View File

@ -25,11 +25,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg2video", "mpeg2video",
"mpeg4", "mpeg4",
"msmpeg4", "msmpeg4",
"dts", "dca",
"ac3", "ac3",
"aac", "aac",
"mp3", "mp3",
"flac", "flac",
"truehd",
"h264_qsv", "h264_qsv",
"hevc_qsv", "hevc_qsv",
"mpeg2_qsv", "mpeg2_qsv",
@ -59,10 +60,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
"aac_at", "aac_at",
"libfdk_aac", "libfdk_aac",
"ac3", "ac3",
"dca",
"libmp3lame", "libmp3lame",
"libopus", "libopus",
"libvorbis", "libvorbis",
"flac", "flac",
"truehd",
"srt", "srt",
"h264_amf", "h264_amf",
"hevc_amf", "hevc_amf",
@ -214,12 +217,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false; return false;
} }
else if (version < MinVersion) // Version is below what we recommend
if (version < MinVersion) // Version is below what we recommend
{ {
_logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion); _logger.LogWarning("FFmpeg validation: The minimum recommended version is {MinVersion}", MinVersion);
return false; return false;
} }
else if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
if (MaxVersion is not null && version > MaxVersion) // Version is above what we recommend
{ {
_logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion); _logger.LogWarning("FFmpeg validation: The maximum recommended version is {MaxVersion}", MaxVersion);
return false; return false;
@ -488,7 +493,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
var found = Regex var found = Regex
.Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline) .Matches(output, @"^\s\S{6}\s(?<codec>[\w|-]+)\s+.+$", RegexOptions.Multiline)
.Cast<Match>()
.Select(x => x.Groups["codec"].Value) .Select(x => x.Groups["codec"].Value)
.Where(x => required.Contains(x)); .Where(x => required.Contains(x));
@ -517,7 +521,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
var found = Regex var found = Regex
.Matches(output, @"^\s\S{3}\s(?<filter>[\w|-]+)\s+.+$", RegexOptions.Multiline) .Matches(output, @"^\s\S{3}\s(?<filter>[\w|-]+)\s+.+$", RegexOptions.Multiline)
.Cast<Match>()
.Select(x => x.Groups["filter"].Value) .Select(x => x.Groups["filter"].Value)
.Where(x => _requiredFilters.Contains(x)); .Where(x => _requiredFilters.Contains(x));

View File

@ -9,6 +9,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Xml; using System.Xml;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
@ -67,6 +68,9 @@ namespace MediaBrowser.MediaEncoding.Probing
"諭吉佳作/men", "諭吉佳作/men",
"//dARTH nULL", "//dARTH nULL",
"Phantom/Ghost", "Phantom/Ghost",
"She/Her/Hers",
"5/8erl in Ehr'n",
"Smith/Kotzen",
}; };
public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol) public MediaInfo GetMediaInfo(InternalMediaInfoResult data, VideoType? videoType, bool isAudio, string path, MediaProtocol protocol)
@ -507,7 +511,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson peoples.Add(new BaseItemPerson
{ {
Name = pair.Value, Name = pair.Value,
Type = PersonType.Writer Type = PersonKind.Writer
}); });
} }
} }
@ -518,7 +522,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson peoples.Add(new BaseItemPerson
{ {
Name = pair.Value, Name = pair.Value,
Type = PersonType.Producer Type = PersonKind.Producer
}); });
} }
} }
@ -529,7 +533,7 @@ namespace MediaBrowser.MediaEncoding.Probing
peoples.Add(new BaseItemPerson peoples.Add(new BaseItemPerson
{ {
Name = pair.Value, Name = pair.Value,
Type = PersonType.Director Type = PersonKind.Director
}); });
} }
} }
@ -1163,7 +1167,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(composer, false)) foreach (var person in Split(composer, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Composer }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Composer });
} }
} }
@ -1171,7 +1175,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(conductor, false)) foreach (var person in Split(conductor, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Conductor }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Conductor });
} }
} }
@ -1179,7 +1183,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(lyricist, false)) foreach (var person in Split(lyricist, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Lyricist }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Lyricist });
} }
} }
@ -1195,7 +1199,7 @@ namespace MediaBrowser.MediaEncoding.Probing
people.Add(new BaseItemPerson people.Add(new BaseItemPerson
{ {
Name = match.Groups["name"].Value, Name = match.Groups["name"].Value,
Type = PersonType.Actor, Type = PersonKind.Actor,
Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value) Role = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(match.Groups["instrument"].Value)
}); });
} }
@ -1207,7 +1211,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(writer, false)) foreach (var person in Split(writer, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Writer }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Writer });
} }
} }
@ -1215,7 +1219,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(arranger, false)) foreach (var person in Split(arranger, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Arranger }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Arranger });
} }
} }
@ -1223,7 +1227,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(engineer, false)) foreach (var person in Split(engineer, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Engineer }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Engineer });
} }
} }
@ -1231,7 +1235,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(mixer, false)) foreach (var person in Split(mixer, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Mixer }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Mixer });
} }
} }
@ -1239,7 +1243,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
foreach (var person in Split(remixer, false)) foreach (var person in Split(remixer, false))
{ {
people.Add(new BaseItemPerson { Name = person, Type = PersonType.Remixer }); people.Add(new BaseItemPerson { Name = person, Type = PersonKind.Remixer });
} }
} }
@ -1491,7 +1495,7 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries) video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i)) .Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonType.Actor }) .Select(i => new BaseItemPerson { Name = i.Trim(), Type = PersonKind.Actor })
.ToArray(); .ToArray();
} }

View File

@ -449,7 +449,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
try try
{ {
_logger.LogInformation("Deleting converted subtitle due to failure: ", outputPath); _logger.LogInformation("Deleting converted subtitle due to failure: {Path}", outputPath);
_fileSystem.DeleteFile(outputPath); _fileSystem.DeleteFile(outputPath);
} }
catch (IOException ex) catch (IOException ex)
@ -624,10 +624,8 @@ namespace MediaBrowser.MediaEncoding.Subtitles
throw new FfmpegException( throw new FfmpegException(
string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath)); string.Format(CultureInfo.InvariantCulture, "ffmpeg subtitle extraction failed for {0} to {1}", inputPath, outputPath));
} }
else
{ _logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
_logger.LogInformation("ffmpeg subtitle extraction completed for {InputPath} to {OutputPath}", inputPath, outputPath);
}
if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase)) if (string.Equals(outputCodec, "ass", StringComparison.OrdinalIgnoreCase))
{ {

Some files were not shown because too many files have changed in this diff Show More