Merge branch 'master' into fix-hwa-video-rotation

This commit is contained in:
Nyanmisaka 2024-07-23 15:37:33 +08:00 committed by GitHub
commit 00088c2954
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
317 changed files with 6221 additions and 3246 deletions

View File

@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"dotnet-ef": { "dotnet-ef": {
"version": "8.0.3", "version": "8.0.7",
"commands": [ "commands": [
"dotnet-ef" "dotnet-ef"
] ]

View File

@ -1,58 +1,131 @@
name: Issue Report name: Issue Report
description: File an issue report description: File an issue report
title: "[Issue]: "
labels: [bug, triage] labels: [bug, triage]
body: body:
- type: markdown - type: markdown
id: introduction
attributes: attributes:
value: | value: |
Thanks for taking the time to report an issue. Before submitting a report, please do the following: ### Thank you for taking the time to report an issue!
1. Please head to our forum or chat rooms and troubleshoot with volunteers if you haven't already. Links can be found here: https://jellyfin.org/contact/ Please keep in mind that Jellyfin is a [free and open-source](https://jellyfin.org/docs/general/about) project, made up entirely and exclusively of **volunteers** who donate their free time to the project.
2. Please search the bug tracker for similar issues. If you do find one, please comment there instead of opening a new bug report. - type: checkboxes
3. If you decide to open a new report, please provide as much detail as possible. id: before-posting
4. Please **ONLY** report **ONE** issue per report. If you are experiencing multiple issues, please open multiple reports.
- type: textarea
id: what-happened
attributes: attributes:
label: Please describe your bug label: "This issue respects the following points:"
description: Also tell us, what did you expect to happen? description: All conditions are **required**. Failure to comply with any of these conditions may cause your issue to be closed without comment.
options:
- label: This is a **bug**, not a question or a configuration issue; Please visit our forum or chat rooms first to troubleshoot with volunteers, before creating a report. The links can be found [here](https://jellyfin.org/contact/).
required: true
- label: This issue is **not** already reported on [GitHub](https://github.com/jellyfin/jellyfin/issues?q=is%3Aopen+is%3Aissue) _(I've searched it)_.
required: true
- label: I'm using an up to date version of Jellyfin Server stable, unstable or master; We generally do not support previous older versions. If possible, please update to the latest version before opening an issue.
required: true
- label: I agree to follow Jellyfin's [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct).
required: true
- label: This report addresses only a single issue; If you encounter multiple issues, kindly create separate reports for each one.
required: true
- type: markdown
id: preliminary-information
attributes:
value: |
### General preliminary information
Please keep the following in mind when creating this issue:
1. Fill in as much of the template as possible. When you are unsure about the relevancy of a section, do include the information requested in that section. Only leave out information in sections when you are completely sure about it not being relevant.
2. Provide as much detail as possible. Do not assume other people to know what is going on.
3. Keep everything readable and structured. Nobody enjoys reading poorly written reports that are difficult to understand.
4. Keep an eye on your report as long as it is open, your involvement might be requested at a later moment.
5. Keep the title short and descriptive. The title is not the place to write down a full description of the issue.
6. When deciding to leave out information in a field, leave it blank and empty. Avoid writing things such as `n/a` for empty fields.
- type: textarea
id: bug-description
attributes:
label: Description of the bug
description: Please provide a detailed description on the bug you encountered, in a readable and comprehensible way.
placeholder: | placeholder: |
The more information that you are able to provide, the better. Did you do anything before this happened? Did you upgrade or change anything? Any screenshots or logs you can provide will be helpful. After upgrading to version x.y.z of Jellyfin, the "login disclaimer" is showing incorrect text. It appears to me that it is appending the server name to the end of the login disclaimer, and showing that to a user. It might be a regression from pull request x. I have tried rebooting my host as well as my container multiple times. I tested this functionality on different clients, and it happens to all the tested clients (client x, y, z), that support the login disclaimer functionality. This makes me believe it is a server side issue.
If you are using an old release of Jellyfin, please also explain why.
validations: validations:
required: true required: true
- type: textarea - type: textarea
id: repro-steps id: repro-steps
attributes: attributes:
label: Reproduction Steps label: Reproduction steps
description: Reproduction steps should be complete and self-contained. Anyone can reproduce this issue by following these steps. Furthermore, the steps should be clear and easy to follow.
placeholder: | placeholder: |
1. In this environment... 1. Sign in on the Jellyfin web client, with an admin account, using a browser of your choice.
2. With this config... 2. Navigate to the dashboard.
3. Run '...' 3. Select "general".
4. See error... 4. Change the login disclaimer to something like "I am a cool disclaimer!"
5. Save the settings.
6. Sign out.
7. Make sure you are on the sign in screen. Otherwise, navigate to the sign in screen manually.
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: What is the current _bug_ behavior?
description: Write down the incorrect behavior that currently happens after following the reproduction steps.
placeholder: |
The login disclaimer on the sign in screen has the server name appended to the text. The text shown is: "I am a cool disclaimer!jellyfinserver".
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: What is the expected _correct_ behavior?
description: Write down the correct expected behavior that is supposed to happen after following the reproduction steps.
placeholder: |
The login disclaimer on the sign in screen should only show the configured text. The text that should be shown is: "I am a cool disclaimer!".
validations: validations:
required: true required: true
- type: dropdown - type: dropdown
id: version id: version
attributes: attributes:
label: Jellyfin Version label: Jellyfin Server version
description: What version of Jellyfin are you running? description: What version of Jellyfin are you using?
options: options:
- 10.8.13 - 10.9.7
- 10.8.12 - Master
- 10.8.11 or older (please specify) - Unstable
- Unstable (master branch) - Older*
validations: validations:
required: true required: true
- type: input - type: input
id: version-other id: version-master
attributes: attributes:
label: "if other:" label: "Specify commit id"
placeholder: Other description: Fill in this field in case the option 'master' is selected. Provide the commit id it was built on.
placeholder: |
610e56baafc3011e1bfa043bdabb567bda0c2ab0
- type: input
id: version-unstable
attributes:
label: "Specify unstable release number"
description: Fill in this field in case the option 'unstable' is selected. Provide the unstable release number.
placeholder: |
2024050906
- type: input
id: version-older
attributes:
label: "Specify version number"
description: Fill in this field in case the option 'older' is selected. Provide the version number.
placeholder: |
x.y.z
- type: input
id: build-version
attributes:
label: "Specify the build version"
description: Please provide the build version that is shown in the dashboard.
validations:
required: true
- type: textarea - type: textarea
id: environment-information
attributes: attributes:
label: Environment label: Environment
description: | description: |
Accurately fill in as much environment details as possible. If a certain environment field is not shown in the template below, but you consider useful information, please include it.
Examples: Examples:
- **OS**: [e.g. Debian 11, Windows 10] - **OS**: [e.g. Debian 11, Windows 10]
- **Linux Kernel**: [e.g. none, 5.15, 6.1, etc.] - **Linux Kernel**: [e.g. none, 5.15, 6.1, etc.]
@ -87,21 +160,22 @@ body:
validations: validations:
required: true required: true
- type: markdown - type: markdown
id: general-information-logs
attributes: attributes:
value: | value: |
When providing logs, please keep the following things in mind. When providing logs, please keep the following things in mind:
1. **DO NOT** use external paste services. 1. **DO NOT** use external paste services. If logs are too large to paste into the field, upload them as text files.
2. Please provide complete logs. 2. Please provide complete logs.
- For server logs, include everything you think is important plus *10 lines before and after* - For server logs, ensure to capture all relevant information, encompassing both the events leading up to and following the occurrence of the issue. Typically, providing 10 *lines preceding and succeeding* the problem should be adequate.
- For ffmpeg logs, please provide the entire file unmodified. - For ffmpeg logs, please provide the entire file unmodified.
3. Please do not run logs through any translation program. Especially beware if your browser translates pages by default. 3. Please do not run logs through any translation program. We exclusively accept raw, untranslated logs. Particularly exercise caution if your browser automatically translates pages by default.
- Do not forget to censor out personal information such as public IP addresses.
4. Please do not include logs as screenshots, with the only exception being client logs in browsers. 4. Please do not include logs as screenshots, with the only exception being client logs in browsers.
- type: textarea - type: textarea
id: logs id: jellyfin-logs
attributes: attributes:
label: Jellyfin logs label: Jellyfin logs
description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs. description: Please copy and paste any relevant log output. This can be found in Dashboard > Logs.
placeholder: For playback issues, browser/client and FFmpeg logs may be more useful.
render: shell render: shell
validations: validations:
required: true required: true
@ -109,24 +183,20 @@ body:
id: ffmpeg-logs id: ffmpeg-logs
attributes: attributes:
label: FFmpeg logs label: FFmpeg logs
description: Please copy and paste recent FFmpeg log output. This can be found in Dashboard > Logs > FFmpeg*.log. description: Relevant FFmpeg log output. This can be found in Dashboard > Logs > FFmpeg*.log. This field is considered mandatory for transcoding related issues. It's also important to include the specific codec details.
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: browser-logs
attributes: attributes:
label: Please attach any browser or client logs here label: Client / Browser logs
placeholder: Access browser logs by using the F12 to bring up the console. Screenshots are typically easier to read than raw logs. For clients such as Android or iOS, please see our documentation. description: Access browser logs by using the F12 to bring up the console. Screenshots are typically easier to read than raw logs. For clients such as Android or iOS, please see our documentation.
- type: textarea - type: textarea
id: screenshots id: screenshots
attributes: attributes:
label: Please attach any screenshots here label: Relevant screenshots or videos
placeholder: Images can be pasted directly into the textbox and will be hosted by github. description: Attach relevant screenshots or videos related to this report.
- type: checkboxes - type: textarea
id: terms id: additional-information
attributes: attributes:
label: Code of Conduct label: Additional information
description: By submitting this issue, you agree to follow our [Code of Conduct](https://jellyfin.org/docs/general/community-standards.html#code-of-conduct) description: Any additional information that might be useful to this issue.
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@ -20,18 +20,18 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 uses: github/codeql-action/init@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 uses: github/codeql-action/autobuild@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9 uses: github/codeql-action/analyze@4fa2a7953630fd2f3fb380f21be14ede0169dd4f # v3.25.12

View File

@ -3,6 +3,8 @@ on:
push: push:
branches: branches:
- master - master
tags:
- 'v*'
pull_request_target: pull_request_target:
permissions: {} permissions: {}
@ -14,18 +16,18 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
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@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: openapi-head name: openapi-head
retention-days: 14 retention-days: 14
@ -39,7 +41,7 @@ jobs:
permissions: read-all permissions: read-all
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
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 }}
@ -53,13 +55,13 @@ jobs:
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF) ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
git checkout --progress --force $ANCESTOR_REF git checkout --progress --force $ANCESTOR_REF
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
with: with:
dotnet-version: '8.0.x' dotnet-version: '8.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@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@0b2256b8c012f0828dc542b3febcab082c67f72b # v4.3.4
with: with:
name: openapi-base name: openapi-base
retention-days: 14 retention-days: 14
@ -78,12 +80,12 @@ jobs:
- openapi-base - openapi-base
steps: steps:
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
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@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: openapi-base name: openapi-base
path: openapi-base path: openapi-base
@ -99,13 +101,26 @@ jobs:
- id: read-diff - id: read-diff
name: Read openapi-diff output name: Read openapi-diff output
run: | run: |
# Read and fix markdown
body=$(cat openapi-changes.md) body=$(cat openapi-changes.md)
body="${body//'%'/'%25'}" # Write to workflow summary
body="${body//$'\n'/'%0A'}" echo "$body" >> $GITHUB_STEP_SUMMARY
body="${body//$'\r'/'%0D'}" # Set ApiChanged var
echo ::set-output name=body::$body if [ "$body" != '' ]; then
echo "ApiChanged=1" >> "$GITHUB_OUTPUT"
else
echo "ApiChanged=0" >> "$GITHUB_OUTPUT"
fi
# Add header/footer for diff comment
echo '<!--openapi-diff-workflow-comment-->' > openapi-changes-reply.md
echo "<details>" >> openapi-changes-reply.md
echo "<summary>Changes in OpenAPI specification found. Expand to see details.</summary>" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "$body" >> openapi-changes-reply.md
echo "" >> openapi-changes-reply.md
echo "</details>" >> openapi-changes-reply.md
- name: Find difference comment - name: Find difference comment
uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3.0.0 uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
id: find-comment id: find-comment
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
@ -113,22 +128,15 @@ jobs:
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@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.body != '' }} if: ${{ steps.read-diff.outputs.ApiChanged == '1' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace edit-mode: replace
body: | body-path: openapi-changes-reply.md
<!--openapi-diff-workflow-comment-->
<details>
<summary>Changes in OpenAPI specification found. Expand to see details.</summary>
${{ steps.read-diff.outputs.body }}
</details>
- name: Edit difference comment (unchanged) - name: Edit difference comment (unchanged)
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
@ -138,11 +146,9 @@ jobs:
No changes to OpenAPI specification found. See history of this comment for previous changes. No changes to OpenAPI specification found. See history of this comment for previous changes.
publish: publish-unstable:
name: OpenAPI - Publish Unstable Spec name: OpenAPI - Publish Unstable Spec
if: | if: ${{ github.event_name != 'pull_request_target' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
github.event_name != 'pull_request_target' &&
contains(github.repository_owner, 'jellyfin')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- openapi-head - openapi-head
@ -152,7 +158,7 @@ jobs:
run: |- run: |-
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with: with:
name: openapi-head name: openapi-head
path: openapi-head path: openapi-head
@ -174,6 +180,12 @@ jobs:
debug: false debug: false
script_stop: false script_stop: false
script: | script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi" TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/unstable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )" LAST_SPEC="$( ls -lt ${TGT_DIR}/unstable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish # If new and previous spec don't differ (diff retcode 0), remove incoming and finish
@ -187,10 +199,73 @@ jobs:
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
# Move current jellyfin-openapi-unstable.json symlink to jellyfin-openapi-unstable_previous.json # Move current jellyfin-openapi-unstable.json symlink to jellyfin-openapi-unstable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-unstable.json ${TGT_DIR}/jellyfin-openapi-unstable_previous.json sudo mv ${TGT_DIR}/jellyfin-openapi-unstable.json ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
# Create new jellyfin-openapi-stable.json symlink # Create new jellyfin-openapi-unstable.json symlink
sudo ln -s unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-unstable.json sudo ln -s unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-unstable.json
# Check that the previous openapi spec is correct # Check that the previous openapi unstable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-unstable_previous.json )" != "unstable/${LAST_SPEC}" ]]; then if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-unstable_previous.json )" != "unstable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
fi fi
) 200>/run/workflows/openapi-unstable.lock
publish-stable:
name: OpenAPI - Publish Stable Spec
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest
needs:
- openapi-head
steps:
- name: Set version number
id: version
run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4.1.8
with:
name: openapi-head
path: openapi-head
- name: Upload openapi.json (stable) to repository server
uses: appleboy/scp-action@917f8b81dfc1ccd331fef9e2d61bdc6c8be94634 # v0.1.7
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
source: openapi-head/openapi.json
strip_components: 1
target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
- name: Move openapi.json (stable) into place
uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3
with:
host: "${{ secrets.REPO_HOST }}"
username: "${{ secrets.REPO_USER }}"
key: "${{ secrets.REPO_KEY }}"
debug: false
script_stop: false
script: |
if ! test -d /run/workflows; then
sudo mkdir -p /run/workflows
sudo chown ${{ secrets.REPO_USER }} /run/workflows
fi
(
flock -x -w 300 200 || exit 1
TGT_DIR="/srv/repository/main/openapi"
LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
# If new and previous spec don't differ (diff retcode 0), remove incoming and finish
if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
exit 0
fi
# Move new spec into place
sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
# Delete previous jellyfin-openapi-stable_previous.json
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
# Create new jellyfin-openapi-stable.json symlink
sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
# Check that the previous openapi stable spec link is correct
if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
fi
) 200>/run/workflows/openapi-stable.lock

View File

@ -19,9 +19,9 @@ jobs:
runs-on: "${{ matrix.os }}" runs-on: "${{ matrix.os }}"
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/setup-dotnet@4d6c8fcf3c8f7a60068d26b594648e99df24cee3 # v4.0.0 - uses: actions/setup-dotnet@6bd8b7f7774af54e05809fcc5431931b3eb1ddee # v4.0.1
with: with:
dotnet-version: ${{ env.SDK_VERSION }} dotnet-version: ${{ env.SDK_VERSION }}
@ -34,7 +34,7 @@ jobs:
--verbosity minimal --verbosity minimal
- name: Merge code coverage results - name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@3e39bd1b454c2bac14560547e4394f9317672705 # 5.2.4 uses: danielpalme/ReportGenerator-GitHub-Action@5808021ec4deecb0ab3da051d49b4ce65fcc20af # 5.3.8
with: with:
reports: "**/coverage.cobertura.xml" reports: "**/coverage.cobertura.xml"
targetdir: "merged/" targetdir: "merged/"

View File

@ -24,7 +24,7 @@ jobs:
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@ -51,7 +51,7 @@ jobs:
reactions: eyes reactions: eyes
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@ -128,11 +128,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull in script - name: pull in script
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
repository: jellyfin/jellyfin-triage-script repository: jellyfin/jellyfin-triage-script
- name: install python - name: install python
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0 uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
with: with:
python-version: '3.12' python-version: '3.12'
cache: 'pip' cache: 'pip'

View File

@ -10,11 +10,11 @@ jobs:
issues: write issues: write
steps: steps:
- name: pull in script - name: pull in script
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
repository: jellyfin/jellyfin-triage-script repository: jellyfin/jellyfin-triage-script
- name: install python - name: install python
uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 uses: actions/setup-python@39cd14951b08e74b54015e9e001cdefcf80e669f # v5.1.1
with: with:
python-version: '3.12' python-version: '3.12'
cache: 'pip' cache: 'pip'

View File

@ -15,7 +15,7 @@ jobs:
if: ${{ github.repository == 'jellyfin/jellyfin' }} if: ${{ github.repository == 'jellyfin/jellyfin' }}
steps: steps:
- name: Apply label - name: Apply label
uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0 uses: eps1lon/actions-label-merge-conflict@1b1b1fcde06a9b3d089f3464c96417961dde1168 # v3.0.2
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'

View File

@ -33,7 +33,7 @@ jobs:
yq-version: v4.9.8 yq-version: v4.9.8
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
ref: ${{ env.TAG_BRANCH }} ref: ${{ env.TAG_BRANCH }}
@ -66,7 +66,7 @@ jobs:
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }} NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with: with:
ref: ${{ env.TAG_BRANCH }} ref: ${{ env.TAG_BRANCH }}

View File

@ -183,6 +183,9 @@
- [btopherjohnson](https://github.com/btopherjohnson) - [btopherjohnson](https://github.com/btopherjohnson)
- [GeorgeH005](https://github.com/GeorgeH005) - [GeorgeH005](https://github.com/GeorgeH005)
- [Vedant](https://github.com/viktory36/) - [Vedant](https://github.com/viktory36/)
- [NotSaifA](https://github.com/NotSaifA)
- [HonestlyWhoKnows](https://github.com/honestlywhoknows)
- [ItsAllAboutTheCode](https://github.com/ItsAllAboutTheCode)
# Emby Contributors # Emby Contributors
@ -255,3 +258,4 @@
- [JPUC1143](https://github.com/Jpuc1143/) - [JPUC1143](https://github.com/Jpuc1143/)
- [0x25CBFC4F](https://github.com/0x25CBFC4F) - [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [Robert Lützner](https://github.com/rluetzner) - [Robert Lützner](https://github.com/rluetzner)
- [Nathan McCrina](https://github.com/nfmccrina)

View File

@ -4,7 +4,7 @@
</PropertyGroup> </PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.--> <!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies"> <ItemGroup Label="Package Dependencies">
<PackageVersion Include="AsyncKeyedLock" Version="6.3.4" /> <PackageVersion Include="AsyncKeyedLock" Version="6.4.2" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" /> <PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" /> <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" /> <PackageVersion Include="AutoFixture" Version="4.18.1" />
@ -13,43 +13,42 @@
<PackageVersion Include="BlurHashSharp" Version="1.3.2" /> <PackageVersion Include="BlurHashSharp" Version="1.3.2" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" /> <PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="Diacritics" Version="3.3.27" /> <PackageVersion Include="Diacritics" Version="3.3.29" />
<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="4.2.3" /> <PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.5.0" />
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" /> <PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.1" /> <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0.2" />
<PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" /> <PackageVersion Include="ICU4N.Transliterator" Version="60.1.0-alpha.356" />
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.7" /> <PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="3.6.13" /> <PackageVersion Include="libse" Version="4.0.5" />
<PackageVersion Include="LrcParser" Version="2023.524.0" /> <PackageVersion Include="LrcParser" Version="2023.524.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" /> <PackageVersion Include="MetaBrainz.MusicBrainz" Version="6.1.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.3" /> <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="8.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.3" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" /> <PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.3" /> <PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.3" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.1" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.3" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.3" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" /> <PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="MimeTypes" Version="2.4.0" /> <PackageVersion Include="MimeTypes" Version="2.4.0" />
<PackageVersion Include="Mono.Nat" Version="3.0.4" /> <PackageVersion Include="Mono.Nat" Version="3.0.4" />
<PackageVersion Include="Moq" Version="4.18.4" /> <PackageVersion Include="Moq" Version="4.18.4" />
@ -60,33 +59,33 @@
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" /> <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
<PackageVersion Include="prometheus-net" Version="8.2.1" /> <PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" /> <PackageVersion Include="Serilog.AspNetCore" Version="8.0.1" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" /> <PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.0" /> <PackageVersion Include="Serilog.Settings.Configuration" Version="8.0.2" />
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" /> <PackageVersion Include="Serilog.Sinks.Async" Version="2.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.1" /> <PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" /> <PackageVersion Include="Serilog.Sinks.File" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" /> <PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.1" />
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" /> <PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
<PackageVersion Include="SharpFuzz" Version="2.1.1" /> <PackageVersion Include="SharpFuzz" Version="2.1.1" />
<PackageVersion Include="SkiaSharp" Version="2.88.7" /> <PackageVersion Include="SkiaSharp" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.7" /> <PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.8" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.8" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> <PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" /> <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="1.0.0.17" /> <PackageVersion Include="Svg.Skia" Version="1.0.0.18" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" /> <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
<PackageVersion Include="System.Globalization" Version="4.3.0" /> <PackageVersion Include="System.Globalization" Version="4.3.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" /> <PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" /> <PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.3" /> <PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.0" /> <PackageVersion Include="System.Threading.Tasks.Dataflow" Version="8.0.1" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" /> <PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="TMDbLib" Version="2.2.0" /> <PackageVersion Include="TMDbLib" Version="2.2.0" />
<PackageVersion Include="UTF.Unknown" Version="2.5.1" /> <PackageVersion Include="UTF.Unknown" Version="2.5.1" />
<PackageVersion Include="Xunit.Priority" Version="1.1.6" /> <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.7" /> <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
<PackageVersion Include="xunit" Version="2.7.0" /> <PackageVersion Include="xunit" Version="2.9.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -537,6 +537,12 @@ namespace Emby.Naming.Common
"extras", "extras",
MediaType.Video), MediaType.Video),
new ExtraRule(
ExtraType.Unknown,
ExtraRuleType.DirectoryName,
"extra",
MediaType.Video),
new ExtraRule( new ExtraRule(
ExtraType.Unknown, ExtraType.Unknown,
ExtraRuleType.DirectoryName, ExtraRuleType.DirectoryName,

View File

@ -36,7 +36,7 @@
<PropertyGroup> <PropertyGroup>
<Authors>Jellyfin Contributors</Authors> <Authors>Jellyfin Contributors</Authors>
<PackageId>Jellyfin.Naming</PackageId> <PackageId>Jellyfin.Naming</PackageId>
<VersionPrefix>10.9.0</VersionPrefix> <VersionPrefix>10.10.0</VersionPrefix>
<RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl> <RepositoryUrl>https://github.com/jellyfin/jellyfin</RepositoryUrl>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup> </PropertyGroup>

View File

@ -107,7 +107,7 @@ namespace Emby.Naming.ExternalFiles
pathInfo.Language = culture.ThreeLetterISOLanguageName; pathInfo.Language = culture.ThreeLetterISOLanguageName;
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);
} }
else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Contains(s, StringComparison.OrdinalIgnoreCase))) else if (_namingOptions.MediaHearingImpairedFlags.Any(s => currentSliceWithoutSeparator.Equals(s, StringComparison.OrdinalIgnoreCase)))
{ {
pathInfo.IsHearingImpaired = true; pathInfo.IsHearingImpaired = true;
extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase); extraString = extraString.Replace(currentSlice, string.Empty, StringComparison.OrdinalIgnoreCase);

View File

@ -0,0 +1,44 @@
using System;
using System.Linq;
using MediaBrowser.Model.Entities;
namespace Emby.Naming.TV;
/// <summary>
/// Helper class for TV metadata parsing.
/// </summary>
public static class TvParserHelpers
{
private static readonly string[] _continuingState = ["Pilot", "Returning Series", "Returning"];
private static readonly string[] _endedState = ["Cancelled", "Canceled"];
/// <summary>
/// Tries to parse a string into <see cref="SeriesStatus"/>.
/// </summary>
/// <param name="status">The status string.</param>
/// <param name="enumValue">The <see cref="SeriesStatus"/>.</param>
/// <returns>Returns true if parsing was successful.</returns>
public static bool TryParseSeriesStatus(string status, out SeriesStatus? enumValue)
{
if (Enum.TryParse(status, true, out SeriesStatus seriesStatus))
{
enumValue = seriesStatus;
return true;
}
if (_continuingState.Contains(status, StringComparer.OrdinalIgnoreCase))
{
enumValue = SeriesStatus.Continuing;
return true;
}
if (_endedState.Contains(status, StringComparer.OrdinalIgnoreCase))
{
enumValue = SeriesStatus.Ended;
return true;
}
enumValue = null;
return false;
}
}

View File

@ -104,6 +104,6 @@ namespace Emby.Server.Implementations.AppBase
/// Gets the folder path to the temp directory within the cache folder. /// Gets the folder path to the temp directory within the cache folder.
/// </summary> /// </summary>
/// <value>The temp directory.</value> /// <value>The temp directory.</value>
public string TempDirectory => Path.Combine(CachePath, "temp"); public string TempDirectory => Path.Join(Path.GetTempPath(), "jellyfin");
} }
} }

View File

@ -127,15 +127,11 @@ namespace Emby.Server.Implementations.AppBase
if (_configurationFactories is null) if (_configurationFactories is null)
{ {
_configurationFactories = new[] { factory }; _configurationFactories = [factory];
} }
else else
{ {
var oldLen = _configurationFactories.Length; _configurationFactories = [.._configurationFactories, factory];
var arr = new IConfigurationFactory[oldLen + 1];
_configurationFactories.CopyTo(arr, 0);
arr[oldLen] = factory;
_configurationFactories = arr;
} }
_configurationStores = _configurationFactories _configurationStores = _configurationFactories

View File

@ -109,13 +109,13 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// The disposable parts. /// The disposable parts.
/// </summary> /// </summary>
private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new(); private readonly ConcurrentBag<IDisposable> _disposableParts = new();
private readonly DeviceId _deviceId; private readonly DeviceId _deviceId;
private readonly IConfiguration _startupConfig; private readonly IConfiguration _startupConfig;
private readonly IXmlSerializer _xmlSerializer; private readonly IXmlSerializer _xmlSerializer;
private readonly IStartupOptions _startupOptions; private readonly IStartupOptions _startupOptions;
private readonly IPluginManager _pluginManager; private readonly PluginManager _pluginManager;
private List<Type> _creatingInstances; private List<Type> _creatingInstances;
@ -161,7 +161,7 @@ namespace Emby.Server.Implementations
ApplicationPaths.PluginsPath, ApplicationPaths.PluginsPath,
ApplicationVersion); ApplicationVersion);
_disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); _disposableParts.Add(_pluginManager);
} }
/// <summary> /// <summary>
@ -360,7 +360,7 @@ namespace Emby.Server.Implementations
{ {
foreach (var part in parts.OfType<IDisposable>()) foreach (var part in parts.OfType<IDisposable>())
{ {
_disposableParts.TryAdd(part, byte.MinValue); _disposableParts.Add(part);
} }
} }
@ -381,7 +381,7 @@ namespace Emby.Server.Implementations
{ {
foreach (var part in parts.OfType<IDisposable>()) foreach (var part in parts.OfType<IDisposable>())
{ {
_disposableParts.TryAdd(part, byte.MinValue); _disposableParts.Add(part);
} }
} }
@ -422,7 +422,7 @@ namespace Emby.Server.Implementations
// Initialize runtime stat collection // Initialize runtime stat collection
if (ConfigurationManager.Configuration.EnableMetrics) if (ConfigurationManager.Configuration.EnableMetrics)
{ {
DotNetRuntimeStatsBuilder.Default().StartCollecting(); _disposableParts.Add(DotNetRuntimeStatsBuilder.Default().StartCollecting());
} }
var networkConfiguration = ConfigurationManager.GetNetworkConfiguration(); var networkConfiguration = ConfigurationManager.GetNetworkConfiguration();
@ -457,7 +457,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IServerConfigurationManager>(ConfigurationManager);
serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager); serviceCollection.AddSingleton<IConfigurationManager>(ConfigurationManager);
serviceCollection.AddSingleton<IApplicationHost>(this); serviceCollection.AddSingleton<IApplicationHost>(this);
serviceCollection.AddSingleton(_pluginManager); serviceCollection.AddSingleton<IPluginManager>(_pluginManager);
serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths); serviceCollection.AddSingleton<IApplicationPaths>(ApplicationPaths);
serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>(); serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>();
@ -664,7 +664,8 @@ namespace Emby.Server.Implementations
GetExports<IMetadataService>(), GetExports<IMetadataService>(),
GetExports<IMetadataProvider>(), GetExports<IMetadataProvider>(),
GetExports<IMetadataSaver>(), GetExports<IMetadataSaver>(),
GetExports<IExternalId>()); GetExports<IExternalId>(),
GetExports<IExternalUrlProvider>());
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
} }
@ -965,7 +966,7 @@ namespace Emby.Server.Implementations
Logger.LogInformation("Disposing {Type}", type.Name); Logger.LogInformation("Disposing {Type}", type.Name);
foreach (var (part, _) in _disposableParts) foreach (var part in _disposableParts.ToArray())
{ {
var partType = part.GetType(); var partType = part.GetType();
if (partType == type) if (partType == type)

View File

@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Collections
var name = _localizationManager.GetLocalizedString("Collections"); var name = _localizationManager.GetLocalizedString("Collections");
await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.BoxSets, libraryOptions, true).ConfigureAwait(false); await _libraryManager.AddVirtualFolder(name, CollectionTypeOptions.boxsets, libraryOptions, true).ConfigureAwait(false);
return FindFolders(path).First(); return FindFolders(path).First();
} }

View File

@ -19,7 +19,8 @@ namespace Emby.Server.Implementations
{ FfmpegAnalyzeDurationKey, "200M" }, { FfmpegAnalyzeDurationKey, "200M" },
{ PlaylistsAllowDuplicatesKey, bool.FalseString }, { PlaylistsAllowDuplicatesKey, bool.FalseString },
{ BindToUnixSocketKey, bool.FalseString }, { BindToUnixSocketKey, bool.FalseString },
{ SqliteCacheSizeKey, "20000" } { SqliteCacheSizeKey, "20000" },
{ SqliteDisableSecondLevelCacheKey, bool.FalseString }
}; };
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using Microsoft.Data.Sqlite; using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -13,6 +14,8 @@ namespace Emby.Server.Implementations.Data
public abstract class BaseSqliteRepository : IDisposable public abstract class BaseSqliteRepository : IDisposable
{ {
private bool _disposed = false; private bool _disposed = false;
private SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
private SqliteConnection _writeConnection;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class. /// Initializes a new instance of the <see cref="BaseSqliteRepository"/> class.
@ -28,17 +31,6 @@ namespace Emby.Server.Implementations.Data
/// </summary> /// </summary>
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>
@ -98,9 +90,55 @@ namespace Emby.Server.Implementations.Data
} }
} }
protected SqliteConnection GetConnection() protected ManagedConnection GetConnection(bool readOnly = false)
{ {
var connection = new SqliteConnection($"Filename={DbFilePath}"); if (!readOnly)
{
_writeLock.Wait();
if (_writeConnection is not null)
{
return new ManagedConnection(_writeConnection, _writeLock);
}
var writeConnection = new SqliteConnection($"Filename={DbFilePath};Pooling=False");
writeConnection.Open();
if (CacheSize.HasValue)
{
writeConnection.Execute("PRAGMA cache_size=" + CacheSize.Value);
}
if (!string.IsNullOrWhiteSpace(LockingMode))
{
writeConnection.Execute("PRAGMA locking_mode=" + LockingMode);
}
if (!string.IsNullOrWhiteSpace(JournalMode))
{
writeConnection.Execute("PRAGMA journal_mode=" + JournalMode);
}
if (JournalSizeLimit.HasValue)
{
writeConnection.Execute("PRAGMA journal_size_limit=" + JournalSizeLimit.Value);
}
if (Synchronous.HasValue)
{
writeConnection.Execute("PRAGMA synchronous=" + (int)Synchronous.Value);
}
if (PageSize.HasValue)
{
writeConnection.Execute("PRAGMA page_size=" + PageSize.Value);
}
writeConnection.Execute("PRAGMA temp_store=" + (int)TempStore);
return new ManagedConnection(_writeConnection = writeConnection, _writeLock);
}
var connection = new SqliteConnection($"Filename={DbFilePath};Mode=ReadOnly");
connection.Open(); connection.Open();
if (CacheSize.HasValue) if (CacheSize.HasValue)
@ -135,17 +173,17 @@ namespace Emby.Server.Implementations.Data
connection.Execute("PRAGMA temp_store=" + (int)TempStore); connection.Execute("PRAGMA temp_store=" + (int)TempStore);
return connection; return new ManagedConnection(connection, null);
} }
public SqliteCommand PrepareStatement(SqliteConnection connection, string sql) public SqliteCommand PrepareStatement(ManagedConnection connection, string sql)
{ {
var command = connection.CreateCommand(); var command = connection.CreateCommand();
command.CommandText = sql; command.CommandText = sql;
return command; return command;
} }
protected bool TableExists(SqliteConnection connection, string name) protected bool TableExists(ManagedConnection connection, string name)
{ {
using var statement = PrepareStatement(connection, "select DISTINCT tbl_name from sqlite_master"); using var statement = PrepareStatement(connection, "select DISTINCT tbl_name from sqlite_master");
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
@ -159,7 +197,7 @@ namespace Emby.Server.Implementations.Data
return false; return false;
} }
protected List<string> GetColumnNames(SqliteConnection connection, string table) protected List<string> GetColumnNames(ManagedConnection connection, string table)
{ {
var columnNames = new List<string>(); var columnNames = new List<string>();
@ -174,7 +212,7 @@ namespace Emby.Server.Implementations.Data
return columnNames; return columnNames;
} }
protected void AddColumn(SqliteConnection connection, string table, string columnName, string type, List<string> existingColumnNames) protected void AddColumn(ManagedConnection connection, string table, string columnName, string type, List<string> existingColumnNames)
{ {
if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase)) if (existingColumnNames.Contains(columnName, StringComparison.OrdinalIgnoreCase))
{ {
@ -186,10 +224,7 @@ namespace Emby.Server.Implementations.Data
protected void CheckDisposed() protected void CheckDisposed()
{ {
if (_disposed) ObjectDisposedException.ThrowIf(_disposed, this);
{
throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed.");
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -210,6 +245,24 @@ namespace Emby.Server.Implementations.Data
return; return;
} }
if (dispose)
{
_writeLock.Wait();
try
{
_writeConnection.Dispose();
}
finally
{
_writeLock.Release();
}
_writeLock.Dispose();
}
_writeConnection = null;
_writeLock = null;
_disposed = true; _disposed = true;
} }
} }

View File

@ -0,0 +1,62 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Data.Sqlite;
namespace Emby.Server.Implementations.Data;
public sealed class ManagedConnection : IDisposable
{
private readonly SemaphoreSlim? _writeLock;
private SqliteConnection _db;
private bool _disposed = false;
public ManagedConnection(SqliteConnection db, SemaphoreSlim? writeLock)
{
_db = db;
_writeLock = writeLock;
}
public SqliteTransaction BeginTransaction()
=> _db.BeginTransaction();
public SqliteCommand CreateCommand()
=> _db.CreateCommand();
public void Execute(string commandText)
=> _db.Execute(commandText);
public SqliteCommand PrepareStatement(string sql)
=> _db.PrepareStatement(sql);
public IEnumerable<SqliteDataReader> Query(string commandText)
=> _db.Query(commandText);
public void Dispose()
{
if (_disposed)
{
return;
}
if (_writeLock is null)
{
// Read connections are managed with an internal pool
_db.Dispose();
}
else
{
// Write lock is managed by BaseSqliteRepository
// Don't dispose here
_writeLock.Release();
}
_db = null!;
_disposed = true;
}
}

View File

@ -49,8 +49,8 @@ namespace Emby.Server.Implementations.Data
private const string SaveItemCommandText = private const string SaveItemCommandText =
@"replace into TypedBaseItems @"replace into TypedBaseItems
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,NormalizationGain,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@NormalizationGain,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
@ -111,6 +111,7 @@ namespace Emby.Server.Implementations.Data
"DateLastMediaAdded", "DateLastMediaAdded",
"Album", "Album",
"LUFS", "LUFS",
"NormalizationGain",
"CriticRating", "CriticRating",
"IsVirtualItem", "IsVirtualItem",
"SeriesName", "SeriesName",
@ -327,7 +328,6 @@ namespace Emby.Server.Implementations.Data
DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db"); DbFilePath = Path.Combine(_config.ApplicationPaths.DataPath, "library.db");
CacheSize = configuration.GetSqliteCacheSize(); CacheSize = configuration.GetSqliteCacheSize();
ReadConnectionsCount = Environment.ProcessorCount * 2;
} }
/// <inheritdoc /> /// <inheritdoc />
@ -479,6 +479,7 @@ namespace Emby.Server.Implementations.Data
AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames); AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames); AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "NormalizationGain", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames); AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
@ -602,7 +603,7 @@ namespace Emby.Server.Implementations.Data
transaction.Commit(); transaction.Commit();
} }
private void SaveItemsInTransaction(SqliteConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples) private void SaveItemsInTransaction(ManagedConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
{ {
using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText)) using (var saveItemStatement = PrepareStatement(db, SaveItemCommandText))
using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId")) using (var deleteAncestorsStatement = PrepareStatement(db, "delete from AncestorIds where ItemId=@ItemId"))
@ -889,6 +890,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@Album", item.Album); saveItemStatement.TryBind("@Album", item.Album);
saveItemStatement.TryBind("@LUFS", item.LUFS); saveItemStatement.TryBind("@LUFS", item.LUFS);
saveItemStatement.TryBind("@NormalizationGain", item.NormalizationGain);
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem); saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
if (item is IHasSeries hasSeriesName) if (item is IHasSeries hasSeriesName)
@ -1047,9 +1049,10 @@ namespace Emby.Server.Implementations.Data
foreach (var part in value.SpanSplit('|')) foreach (var part in value.SpanSplit('|'))
{ {
var providerDelimiterIndex = part.IndexOf('='); var providerDelimiterIndex = part.IndexOf('=');
if (providerDelimiterIndex != -1 && providerDelimiterIndex == part.LastIndexOf('=')) // Don't let empty values through
if (providerDelimiterIndex != -1 && part.Length != providerDelimiterIndex + 1)
{ {
item.SetProviderId(part.Slice(0, providerDelimiterIndex).ToString(), part.Slice(providerDelimiterIndex + 1).ToString()); item.SetProviderId(part[..providerDelimiterIndex].ToString(), part[(providerDelimiterIndex + 1)..].ToString());
} }
} }
} }
@ -1261,7 +1264,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
using (var connection = GetConnection()) 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);
@ -1298,16 +1301,15 @@ namespace Emby.Server.Implementations.Data
&& type != typeof(Book) && type != typeof(Book)
&& type != typeof(LiveTvProgram) && type != typeof(LiveTvProgram)
&& type != typeof(AudioBook) && type != typeof(AudioBook)
&& type != typeof(Audio)
&& type != typeof(MusicAlbum); && type != typeof(MusicAlbum);
} }
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query) private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query)
{ {
return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query)); return GetItem(reader, query, HasProgramAttributes(query), HasEpisodeAttributes(query), HasServiceName(query), HasStartDate(query), HasTrailerTypes(query), HasArtistFields(query), HasSeriesFields(query), false);
} }
private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields) private BaseItem GetItem(SqliteDataReader reader, InternalItemsQuery query, bool enableProgramAttributes, bool hasEpisodeAttributes, bool hasServiceName, bool queryHasStartDate, bool hasTrailerTypes, bool hasArtistFields, bool hasSeriesFields, bool skipDeserialization)
{ {
var typeString = reader.GetString(0); var typeString = reader.GetString(0);
@ -1320,7 +1322,7 @@ namespace Emby.Server.Implementations.Data
BaseItem item = null; BaseItem item = null;
if (TypeRequiresDeserialization(type)) if (TypeRequiresDeserialization(type) && !skipDeserialization)
{ {
try try
{ {
@ -1675,6 +1677,11 @@ namespace Emby.Server.Implementations.Data
item.LUFS = lUFS; item.LUFS = lUFS;
} }
if (reader.TryGetSingle(index++, out var normalizationGain))
{
item.NormalizationGain = normalizationGain;
}
if (reader.TryGetSingle(index++, out var criticRating)) if (reader.TryGetSingle(index++, out var criticRating))
{ {
item.CriticRating = criticRating; item.CriticRating = criticRating;
@ -1883,7 +1890,7 @@ namespace Emby.Server.Implementations.Data
CheckDisposed(); CheckDisposed();
var chapters = new List<ChapterInfo>(); var chapters = new List<ChapterInfo>();
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc")) using (var statement = PrepareStatement(connection, "select StartPositionTicks,Name,ImagePath,ImageDateModified from " + ChaptersTableName + " where ItemId = @ItemId order by ChapterIndex asc"))
{ {
statement.TryBind("@ItemId", item.Id); statement.TryBind("@ItemId", item.Id);
@ -1902,7 +1909,7 @@ namespace Emby.Server.Implementations.Data
{ {
CheckDisposed(); CheckDisposed();
using (var connection = GetConnection()) 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("@ItemId", item.Id);
@ -1976,7 +1983,7 @@ namespace Emby.Server.Implementations.Data
transaction.Commit(); transaction.Commit();
} }
private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, SqliteConnection db) private void InsertChapters(Guid idBlob, IReadOnlyList<ChapterInfo> chapters, ManagedConnection db)
{ {
var startIndex = 0; var startIndex = 0;
var limit = 100; var limit = 100;
@ -2318,14 +2325,7 @@ namespace Emby.Server.Implementations.Data
columns.Add(builder.ToString()); columns.Add(builder.ToString());
var oldLen = query.ExcludeItemIds.Length; query.ExcludeItemIds = [.. query.ExcludeItemIds, item.Id, .. item.ExtraIds];
var newLen = oldLen + item.ExtraIds.Length + 1;
var excludeIds = new Guid[newLen];
query.ExcludeItemIds.CopyTo(excludeIds, 0);
excludeIds[oldLen] = item.Id;
item.ExtraIds.CopyTo(excludeIds, oldLen + 1);
query.ExcludeItemIds = excludeIds;
query.ExcludeProviderIds = item.ProviderIds; query.ExcludeProviderIds = item.ProviderIds;
} }
@ -2472,7 +2472,7 @@ namespace Emby.Server.Implementations.Data
var commandText = commandTextBuilder.ToString(); var commandText = commandTextBuilder.ToString();
using (new QueryTimeLogger(Logger, commandText)) using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText)) using (var statement = PrepareStatement(connection, commandText))
{ {
if (EnableJoinUserData(query)) if (EnableJoinUserData(query))
@ -2540,7 +2540,7 @@ namespace Emby.Server.Implementations.Data
var commandText = commandTextBuilder.ToString(); var commandText = commandTextBuilder.ToString();
var items = new List<BaseItem>(); var items = new List<BaseItem>();
using (new QueryTimeLogger(Logger, commandText)) using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText)) using (var statement = PrepareStatement(connection, commandText))
{ {
if (EnableJoinUserData(query)) if (EnableJoinUserData(query))
@ -2564,7 +2564,7 @@ namespace Emby.Server.Implementations.Data
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, query.SkipDeserialization);
if (item is not null) if (item is not null)
{ {
items.Add(item); items.Add(item);
@ -2748,7 +2748,7 @@ namespace Emby.Server.Implementations.Data
var list = new List<BaseItem>(); var list = new List<BaseItem>();
var result = new QueryResult<BaseItem>(); var result = new QueryResult<BaseItem>();
using var connection = GetConnection(); using var connection = GetConnection(true);
using var transaction = connection.BeginTransaction(); using var transaction = connection.BeginTransaction();
if (!isReturningZeroItems) if (!isReturningZeroItems)
{ {
@ -2776,7 +2776,7 @@ namespace Emby.Server.Implementations.Data
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, false);
if (item is not null) if (item is not null)
{ {
list.Add(item); list.Add(item);
@ -2833,10 +2833,7 @@ namespace Emby.Server.Implementations.Data
prepend.Add((ItemSortBy.Random, SortOrder.Ascending)); prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
} }
var arr = new (ItemSortBy, SortOrder)[prepend.Count + orderBy.Count]; orderBy = query.OrderBy = [.. prepend, .. orderBy];
prepend.CopyTo(arr, 0);
orderBy.CopyTo(arr, prepend.Count);
orderBy = query.OrderBy = arr;
} }
else if (orderBy.Count == 0) else if (orderBy.Count == 0)
{ {
@ -2933,7 +2930,7 @@ namespace Emby.Server.Implementations.Data
var commandText = commandTextBuilder.ToString(); var commandText = commandTextBuilder.ToString();
var list = new List<Guid>(); var list = new List<Guid>();
using (new QueryTimeLogger(Logger, commandText)) using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText)) using (var statement = PrepareStatement(connection, commandText))
{ {
if (EnableJoinUserData(query)) if (EnableJoinUserData(query))
@ -4197,7 +4194,19 @@ namespace Emby.Server.Implementations.Data
{ {
int index = 0; int index = 0;
string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++)); string includedTags = string.Join(',', query.IncludeInheritedTags.Select(_ => paramName + index++));
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)"); // Episodes do not store inherit tags from their parents in the database, and the tag may be still required by the client.
// In addtion to the tags for the episodes themselves, we need to manually query its parent (the season)'s tags as well.
if (includeTypes.Length == 1 && includeTypes.FirstOrDefault() is BaseItemKind.Episode)
{
whereClauses.Add($"""
((select CleanValue from ItemValues where ItemId=Guid and Type=6 and CleanValue in ({includedTags})) is not null
OR (select CleanValue from ItemValues where ItemId=ParentId and Type=6 and CleanValue in ({includedTags})) is not null)
""");
}
else
{
whereClauses.Add("((select CleanValue from ItemValues where ItemId=Guid and Type=6 and cleanvalue in (" + includedTags + ")) is not null)");
}
} }
else else
{ {
@ -4470,7 +4479,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
transaction.Commit(); transaction.Commit();
} }
private void ExecuteWithSingleParam(SqliteConnection db, string query, Guid value) private void ExecuteWithSingleParam(ManagedConnection db, string query, Guid value)
{ {
using (var statement = PrepareStatement(db, query)) using (var statement = PrepareStatement(db, query))
{ {
@ -4503,7 +4512,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
} }
var list = new List<string>(); var list = new List<string>();
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText.ToString())) using (var statement = PrepareStatement(connection, commandText.ToString()))
{ {
// Run this again to bind the params // Run this again to bind the params
@ -4541,7 +4550,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
} }
var list = new List<PersonInfo>(); var list = new List<PersonInfo>();
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText.ToString())) using (var statement = PrepareStatement(connection, commandText.ToString()))
{ {
// Run this again to bind the params // Run this again to bind the params
@ -4626,7 +4635,7 @@ AND Type = @InternalPersonType)");
return whereClauses; return whereClauses;
} }
private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, SqliteConnection db, SqliteCommand deleteAncestorsStatement) private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, ManagedConnection db, SqliteCommand deleteAncestorsStatement)
{ {
if (itemId.IsEmpty()) if (itemId.IsEmpty())
{ {
@ -4781,7 +4790,7 @@ AND Type = @InternalPersonType)");
var list = new List<string>(); var list = new List<string>();
using (new QueryTimeLogger(Logger, commandText)) using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, commandText)) using (var statement = PrepareStatement(connection, commandText))
{ {
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
@ -4981,8 +4990,8 @@ AND Type = @InternalPersonType)");
var list = new List<(BaseItem, ItemCounts)>(); var list = new List<(BaseItem, ItemCounts)>();
var result = new QueryResult<(BaseItem, ItemCounts)>(); var result = new QueryResult<(BaseItem, ItemCounts)>();
using (new QueryTimeLogger(Logger, commandText)) using (new QueryTimeLogger(Logger, commandText))
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var transaction = connection.BeginTransaction(deferred: true)) using (var transaction = connection.BeginTransaction())
{ {
if (!isReturningZeroItems) if (!isReturningZeroItems)
{ {
@ -5014,7 +5023,7 @@ AND Type = @InternalPersonType)");
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields); var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields, false);
if (item is not null) if (item is not null)
{ {
var countStartColumn = columns.Count - 1; var countStartColumn = columns.Count - 1;
@ -5137,12 +5146,12 @@ AND Type = @InternalPersonType)");
list.AddRange(inheritedTags.Select(i => (6, i))); list.AddRange(inheritedTags.Select(i => (6, i)));
// Remove all invalid values. // Remove all invalid values.
list.RemoveAll(i => string.IsNullOrEmpty(i.Item2)); list.RemoveAll(i => string.IsNullOrWhiteSpace(i.Item2));
return list; return list;
} }
private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, SqliteConnection db) private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, ManagedConnection db)
{ {
if (itemId.IsEmpty()) if (itemId.IsEmpty())
{ {
@ -5161,7 +5170,7 @@ AND Type = @InternalPersonType)");
InsertItemValues(itemId, values, db); InsertItemValues(itemId, values, db);
} }
private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, SqliteConnection db) private void InsertItemValues(Guid id, List<(int MagicNumber, string Value)> values, ManagedConnection db)
{ {
const int Limit = 100; const int Limit = 100;
var startIndex = 0; var startIndex = 0;
@ -5195,12 +5204,6 @@ AND Type = @InternalPersonType)");
var itemValue = currentValueInfo.Value; var itemValue = currentValueInfo.Value;
// Don't save if invalid
if (string.IsNullOrWhiteSpace(itemValue))
{
continue;
}
statement.TryBind("@Type" + index, currentValueInfo.MagicNumber); statement.TryBind("@Type" + index, currentValueInfo.MagicNumber);
statement.TryBind("@Value" + index, itemValue); statement.TryBind("@Value" + index, itemValue);
statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue)); statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue));
@ -5221,24 +5224,25 @@ AND Type = @InternalPersonType)");
throw new ArgumentNullException(nameof(itemId)); throw new ArgumentNullException(nameof(itemId));
} }
ArgumentNullException.ThrowIfNull(people);
CheckDisposed(); CheckDisposed();
using var connection = GetConnection(); using var connection = GetConnection();
using var transaction = connection.BeginTransaction(); using var transaction = connection.BeginTransaction();
// First delete chapters // Delete all existing people first
using var command = connection.CreateCommand(); using var command = connection.CreateCommand();
command.CommandText = "delete from People where ItemId=@ItemId"; command.CommandText = "delete from People where ItemId=@ItemId";
command.TryBind("@ItemId", itemId); command.TryBind("@ItemId", itemId);
command.ExecuteNonQuery(); command.ExecuteNonQuery();
InsertPeople(itemId, people, connection); if (people is not null)
{
InsertPeople(itemId, people, connection);
}
transaction.Commit(); transaction.Commit();
} }
private void InsertPeople(Guid id, List<PersonInfo> people, SqliteConnection db) private void InsertPeople(Guid id, List<PersonInfo> people, ManagedConnection db)
{ {
const int Limit = 100; const int Limit = 100;
var startIndex = 0; var startIndex = 0;
@ -5334,7 +5338,7 @@ AND Type = @InternalPersonType)");
cmdText += " order by StreamIndex ASC"; cmdText += " order by StreamIndex ASC";
using (var connection = GetConnection()) using (var connection = GetConnection(true))
{ {
var list = new List<MediaStream>(); var list = new List<MediaStream>();
@ -5387,7 +5391,7 @@ AND Type = @InternalPersonType)");
transaction.Commit(); transaction.Commit();
} }
private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, SqliteConnection db) private void InsertMediaStreams(Guid id, IReadOnlyList<MediaStream> streams, ManagedConnection db)
{ {
const int Limit = 10; const int Limit = 10;
var startIndex = 0; var startIndex = 0;
@ -5700,13 +5704,17 @@ AND Type = @InternalPersonType)");
item.Rotation = rotation; item.Rotation = rotation;
} }
if (item.Type == MediaStreamType.Subtitle) if (item.Type is MediaStreamType.Audio or MediaStreamType.Subtitle)
{ {
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedDefault = _localization.GetLocalizedString("Default"); item.LocalizedDefault = _localization.GetLocalizedString("Default");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedExternal = _localization.GetLocalizedString("External"); item.LocalizedExternal = _localization.GetLocalizedString("External");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
if (item.Type is MediaStreamType.Subtitle)
{
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedHearingImpaired = _localization.GetLocalizedString("HearingImpaired");
}
} }
return item; return item;
@ -5728,7 +5736,7 @@ AND Type = @InternalPersonType)");
cmdText += " order by AttachmentIndex ASC"; cmdText += " order by AttachmentIndex ASC";
var list = new List<MediaAttachment>(); var list = new List<MediaAttachment>();
using (var connection = GetConnection()) using (var connection = GetConnection(true))
using (var statement = PrepareStatement(connection, cmdText)) using (var statement = PrepareStatement(connection, cmdText))
{ {
statement.TryBind("@ItemId", query.ItemId); statement.TryBind("@ItemId", query.ItemId);
@ -5778,7 +5786,7 @@ AND Type = @InternalPersonType)");
private void InsertMediaAttachments( private void InsertMediaAttachments(
Guid id, Guid id,
IReadOnlyList<MediaAttachment> attachments, IReadOnlyList<MediaAttachment> attachments,
SqliteConnection db, ManagedConnection db,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
const int InsertAtOnce = 10; const int InsertAtOnce = 10;

View File

@ -58,7 +58,8 @@ namespace Emby.Server.Implementations.Data
"create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)", "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
"create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)", "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
"create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)", "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
"create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)")); "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)",
"create index if not exists UserDatasIndex5 on UserDatas (key, userId, lastPlayedDate)"));
if (!userDataTableExists) if (!userDataTableExists)
{ {
@ -85,7 +86,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private void ImportUserIds(SqliteConnection db, IEnumerable<User> users) private void ImportUserIds(ManagedConnection db, IEnumerable<User> users)
{ {
var userIdsWithUserData = GetAllUserIdsWithUserData(db); var userIdsWithUserData = GetAllUserIdsWithUserData(db);
@ -106,7 +107,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private List<Guid> GetAllUserIdsWithUserData(SqliteConnection db) private List<Guid> GetAllUserIdsWithUserData(ManagedConnection db)
{ {
var list = new List<Guid>(); var list = new List<Guid>();
@ -175,7 +176,7 @@ namespace Emby.Server.Implementations.Data
} }
} }
private static void SaveUserData(SqliteConnection db, long internalUserId, string key, UserItemData userData) private static void SaveUserData(ManagedConnection db, long internalUserId, string key, UserItemData userData)
{ {
using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)")) using (var statement = db.PrepareStatement("replace into UserDatas (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)"))
{ {
@ -266,7 +267,7 @@ namespace Emby.Server.Implementations.Data
ArgumentException.ThrowIfNullOrEmpty(key); ArgumentException.ThrowIfNullOrEmpty(key);
using (var connection = GetConnection()) using (var connection = GetConnection(true))
{ {
using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId")) using (var statement = connection.PrepareStatement("select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from UserDatas where key =@Key and userId=@UserId"))
{ {

View File

@ -668,12 +668,13 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>(); dto.ImageBlurHashes ??= new Dictionary<ImageType, Dictionary<string, string>>();
if (!dto.ImageBlurHashes.ContainsKey(image.Type)) if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value))
{ {
dto.ImageBlurHashes[image.Type] = new Dictionary<string, string>(); value = new Dictionary<string, string>();
dto.ImageBlurHashes[image.Type] = value;
} }
dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; value[tag] = image.BlurHash;
} }
return tag; return tag;
@ -897,16 +898,21 @@ namespace Emby.Server.Implementations.Dto
dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
} }
dto.LUFS = item.LUFS; if (item.LUFS.HasValue)
{
// -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
dto.NormalizationGain = -18f - item.LUFS;
}
else if (item.NormalizationGain.HasValue)
{
dto.NormalizationGain = item.NormalizationGain;
}
// Add audio info // Add audio info
if (item is Audio audio) if (item is Audio audio)
{ {
dto.Album = audio.Album; dto.Album = audio.Album;
if (audio.ExtraType.HasValue) dto.ExtraType = audio.ExtraType;
{
dto.ExtraType = audio.ExtraType.Value.ToString();
}
var albumParent = audio.AlbumEntity; var albumParent = audio.AlbumEntity;
@ -1058,10 +1064,7 @@ namespace Emby.Server.Implementations.Dto
dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
} }
if (video.ExtraType.HasValue) dto.ExtraType = video.ExtraType;
{
dto.ExtraType = video.ExtraType.Value.ToString();
}
} }
if (options.ContainsField(ItemFields.MediaStreams)) if (options.ContainsField(ItemFields.MediaStreams))

View File

@ -101,14 +101,14 @@ namespace Emby.Server.Implementations.HttpServer
var pipe = new Pipe(); var pipe = new Pipe();
var writer = pipe.Writer; var writer = pipe.Writer;
ValueWebSocketReceiveResult receiveresult; ValueWebSocketReceiveResult receiveResult;
do do
{ {
// Allocate at least 512 bytes from the PipeWriter // Allocate at least 512 bytes from the PipeWriter
Memory<byte> memory = writer.GetMemory(512); Memory<byte> memory = writer.GetMemory(512);
try try
{ {
receiveresult = await _socket.ReceiveAsync(memory, cancellationToken).ConfigureAwait(false); receiveResult = await _socket.ReceiveAsync(memory, cancellationToken).ConfigureAwait(false);
} }
catch (WebSocketException ex) catch (WebSocketException ex)
{ {
@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.HttpServer
break; break;
} }
int bytesRead = receiveresult.Count; int bytesRead = receiveResult.Count;
if (bytesRead == 0) if (bytesRead == 0)
{ {
break; break;
@ -135,13 +135,13 @@ namespace Emby.Server.Implementations.HttpServer
LastActivityDate = DateTime.UtcNow; LastActivityDate = DateTime.UtcNow;
if (receiveresult.EndOfMessage) if (receiveResult.EndOfMessage)
{ {
await ProcessInternal(pipe.Reader).ConfigureAwait(false); await ProcessInternal(pipe.Reader).ConfigureAwait(false);
} }
} }
while ((_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting) while ((_socket.State == WebSocketState.Open || _socket.State == WebSocketState.Connecting)
&& receiveresult.MessageType != WebSocketMessageType.Close); && receiveResult.MessageType != WebSocketMessageType.Close);
Closed?.Invoke(this, EventArgs.Empty); Closed?.Invoke(this, EventArgs.Empty);
@ -199,13 +199,20 @@ namespace Emby.Server.Implementations.HttpServer
} }
else else
{ {
await OnReceive( try
new WebSocketMessageInfo {
{ await OnReceive(
MessageType = stub.MessageType, new WebSocketMessageInfo
Data = stub.Data?.ToString(), // Data can be null {
Connection = this MessageType = stub.MessageType,
}).ConfigureAwait(false); Data = stub.Data?.ToString(), // Data can be null
Connection = this
}).ConfigureAwait(false);
}
catch (Exception exception)
{
_logger.LogWarning(exception, "Failed to process WebSocket message");
}
} }
} }

View File

@ -48,7 +48,7 @@ namespace Emby.Server.Implementations.HttpServer
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
using var connection = new WebSocketConnection( var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(), _loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket, webSocket,
authorizationInfo, authorizationInfo,
@ -56,17 +56,19 @@ namespace Emby.Server.Implementations.HttpServer
{ {
OnReceive = ProcessWebSocketMessageReceived OnReceive = ProcessWebSocketMessageReceived
}; };
await using (connection.ConfigureAwait(false))
var tasks = new Task[_webSocketListeners.Length];
for (var i = 0; i < _webSocketListeners.Length; ++i)
{ {
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context); var tasks = new Task[_webSocketListeners.Length];
for (var i = 0; i < _webSocketListeners.Length; ++i)
{
tasks[i] = _webSocketListeners[i].ProcessWebSocketConnectedAsync(connection, context);
}
await Task.WhenAll(tasks).ConfigureAwait(false);
await connection.ReceiveAsync().ConfigureAwait(false);
_logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress);
} }
await Task.WhenAll(tasks).ConfigureAwait(false);
await connection.ReceiveAsync().ConfigureAwait(false);
_logger.LogInformation("WS {IP} closed", context.Connection.RemoteIpAddress);
} }
catch (Exception ex) // Otherwise ASP.Net will ignore the exception catch (Exception ex) // Otherwise ASP.Net will ignore the exception
{ {

View File

@ -80,12 +80,14 @@ namespace Emby.Server.Implementations.IO
public virtual string MakeAbsolutePath(string folderPath, string filePath) public virtual string MakeAbsolutePath(string folderPath, string filePath)
{ {
// path is actually a stream // path is actually a stream
if (string.IsNullOrWhiteSpace(filePath) || filePath.Contains("://", StringComparison.Ordinal)) if (string.IsNullOrWhiteSpace(filePath))
{ {
return filePath; return filePath;
} }
if (filePath.Length > 3 && filePath[1] == ':' && filePath[2] == '/') var isAbsolutePath = Path.IsPathRooted(filePath) && (!OperatingSystem.IsWindows() || filePath[0] != '\\');
if (isAbsolutePath)
{ {
// absolute local path // absolute local path
return filePath; return filePath;
@ -97,17 +99,10 @@ namespace Emby.Server.Implementations.IO
return filePath; return filePath;
} }
var firstChar = filePath[0];
if (firstChar == '/')
{
// for this we don't really know
return filePath;
}
var filePathSpan = filePath.AsSpan(); var filePathSpan = filePath.AsSpan();
// relative path // relative path on windows
if (firstChar == '\\') if (filePath[0] == '\\')
{ {
filePathSpan = filePathSpan.Slice(1); filePathSpan = filePathSpan.Slice(1);
} }
@ -394,7 +389,7 @@ namespace Emby.Server.Implementations.IO
var info = new FileInfo(path); var info = new FileInfo(path);
if (info.Exists && if (info.Exists &&
((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) != isHidden) (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden != isHidden)
{ {
if (isHidden) if (isHidden)
{ {
@ -422,8 +417,8 @@ namespace Emby.Server.Implementations.IO
return; return;
} }
if (((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) == readOnly if ((info.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly == readOnly
&& ((info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden) == isHidden) && (info.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden == isHidden)
{ {
return; return;
} }
@ -471,7 +466,7 @@ namespace Emby.Server.Implementations.IO
File.Copy(file1, temp1, true); File.Copy(file1, temp1, true);
File.Copy(file2, file1, true); File.Copy(file2, file1, true);
File.Copy(temp1, file2, true); File.Move(temp1, file2, true);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -122,6 +122,7 @@ namespace Emby.Server.Implementations.Images
} }
await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false);
File.Delete(outputPath);
return ItemUpdateType.ImageUpdate; return ItemUpdateType.ImageUpdate;
} }

View File

@ -11,7 +11,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
namespace Emby.Server.Implementations.Images namespace Emby.Server.Implementations.Images
{ {
@ -33,12 +32,12 @@ namespace Emby.Server.Implementations.Images
Parent = item, Parent = item,
Recursive = true, Recursive = true,
DtoOptions = new DtoOptions(true), DtoOptions = new DtoOptions(true),
ImageTypes = new ImageType[] { ImageType.Primary }, ImageTypes = [ImageType.Primary],
OrderBy = new (ItemSortBy, SortOrder)[] OrderBy =
{ [
(ItemSortBy.IsFolder, SortOrder.Ascending), (ItemSortBy.IsFolder, SortOrder.Ascending),
(ItemSortBy.SortName, SortOrder.Ascending) (ItemSortBy.SortName, SortOrder.Ascending)
}, ],
Limit = 1 Limit = 1
}); });
} }

View File

@ -1,7 +1,10 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
@ -15,5 +18,13 @@ namespace Emby.Server.Implementations.Images
: base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor, libraryManager)
{ {
} }
protected override IReadOnlyList<BaseItem> GetItemsWithImages(BaseItem item)
{
var items = base.GetItemsWithImages(item);
// Ignore any folders because they can have generated collages
return items.Where(i => i is not Folder).ToList();
}
} }
} }

View File

@ -29,7 +29,7 @@ namespace Emby.Server.Implementations.Library
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent) public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent)
{ {
// Don't ignore application folders // Don't ignore application folders
if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture)) if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))

View File

@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
} }
}; };
private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray(); private static readonly Glob[] _globs = Array.ConvertAll(_patterns, p => Glob.Parse(p, _globOptions));
/// <summary> /// <summary>
/// Returns true if the supplied path should be ignored. /// Returns true if the supplied path should be ignored.

View File

@ -1,6 +1,5 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable CA5394
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
@ -18,6 +17,7 @@ using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators; using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.ScheduledTasks.Tasks; using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Emby.Server.Implementations.Sorting;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions; using Jellyfin.Extensions;
@ -89,8 +89,8 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// The _root folder. /// The _root folder.
/// </summary> /// </summary>
private volatile AggregateFolder _rootFolder; private volatile AggregateFolder? _rootFolder;
private volatile UserRootFolder _userRootFolder; private volatile UserRootFolder? _userRootFolder;
private bool _wizardCompleted; private bool _wizardCompleted;
@ -155,17 +155,17 @@ namespace Emby.Server.Implementations.Library
/// <summary> /// <summary>
/// Occurs when [item added]. /// Occurs when [item added].
/// </summary> /// </summary>
public event EventHandler<ItemChangeEventArgs> ItemAdded; public event EventHandler<ItemChangeEventArgs>? ItemAdded;
/// <summary> /// <summary>
/// Occurs when [item updated]. /// Occurs when [item updated].
/// </summary> /// </summary>
public event EventHandler<ItemChangeEventArgs> ItemUpdated; public event EventHandler<ItemChangeEventArgs>? ItemUpdated;
/// <summary> /// <summary>
/// Occurs when [item removed]. /// Occurs when [item removed].
/// </summary> /// </summary>
public event EventHandler<ItemChangeEventArgs> ItemRemoved; public event EventHandler<ItemChangeEventArgs>? ItemRemoved;
/// <summary> /// <summary>
/// Gets the root folder. /// Gets the root folder.
@ -264,7 +264,7 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
/// <param name="sender">The sender.</param> /// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param> /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
private void ConfigurationUpdated(object sender, EventArgs e) private void ConfigurationUpdated(object? sender, EventArgs e)
{ {
var config = _configurationManager.Configuration; var config = _configurationManager.Configuration;
@ -338,7 +338,7 @@ namespace Emby.Server.Implementations.Library
if (item is LiveTvProgram) if (item is LiveTvProgram)
{ {
_logger.LogDebug( _logger.LogDebug(
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name, item.GetType().Name,
item.Name ?? "Unknown name", item.Name ?? "Unknown name",
item.Path ?? string.Empty, item.Path ?? string.Empty,
@ -347,7 +347,7 @@ namespace Emby.Server.Implementations.Library
else else
{ {
_logger.LogInformation( _logger.LogInformation(
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}", "Removing item, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name, item.GetType().Name,
item.Name ?? "Unknown name", item.Name ?? "Unknown name",
item.Path ?? string.Empty, item.Path ?? string.Empty,
@ -366,7 +366,7 @@ namespace Emby.Server.Implementations.Library
} }
_logger.LogDebug( _logger.LogDebug(
"Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", "Deleting metadata path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name, item.GetType().Name,
item.Name ?? "Unknown name", item.Name ?? "Unknown name",
metadataPath, metadataPath,
@ -395,7 +395,7 @@ namespace Emby.Server.Implementations.Library
try try
{ {
_logger.LogInformation( _logger.LogInformation(
"Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}", "Deleting item path, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name, item.GetType().Name,
item.Name ?? "Unknown name", item.Name ?? "Unknown name",
fileSystemInfo.FullName, fileSystemInfo.FullName,
@ -410,6 +410,24 @@ namespace Emby.Server.Implementations.Library
File.Delete(fileSystemInfo.FullName); File.Delete(fileSystemInfo.FullName);
} }
} }
catch (DirectoryNotFoundException)
{
_logger.LogInformation(
"Directory not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name,
item.Name ?? "Unknown name",
fileSystemInfo.FullName,
item.Id);
}
catch (FileNotFoundException)
{
_logger.LogInformation(
"File not found, only removing from database, Type: {Type}, Name: {Name}, Path: {Path}, Id: {Id}",
item.GetType().Name,
item.Name ?? "Unknown name",
fileSystemInfo.FullName,
item.Id);
}
catch (IOException) catch (IOException)
{ {
if (isRequiredForDelete) if (isRequiredForDelete)
@ -443,7 +461,7 @@ namespace Emby.Server.Implementations.Library
ReportItemRemoved(item, parent); ReportItemRemoved(item, parent);
} }
private static IEnumerable<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children) private static List<string> GetMetadataPaths(BaseItem item, IEnumerable<BaseItem> children)
{ {
var list = new List<string> var list = new List<string>
{ {
@ -461,7 +479,7 @@ namespace Emby.Server.Implementations.Library
/// <param name="args">The args.</param> /// <param name="args">The args.</param>
/// <param name="resolvers">The resolvers.</param> /// <param name="resolvers">The resolvers.</param>
/// <returns>BaseItem.</returns> /// <returns>BaseItem.</returns>
private BaseItem ResolveItem(ItemResolveArgs args, IItemResolver[] resolvers) private BaseItem? ResolveItem(ItemResolveArgs args, IItemResolver[]? resolvers)
{ {
var item = (resolvers ?? EntityResolvers).Select(r => Resolve(args, r)) var item = (resolvers ?? EntityResolvers).Select(r => Resolve(args, r))
.FirstOrDefault(i => i is not null); .FirstOrDefault(i => i is not null);
@ -474,7 +492,7 @@ namespace Emby.Server.Implementations.Library
return item; return item;
} }
private BaseItem Resolve(ItemResolveArgs args, IItemResolver resolver) private BaseItem? Resolve(ItemResolveArgs args, IItemResolver resolver)
{ {
try try
{ {
@ -516,16 +534,16 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5(); return key.GetMD5();
} }
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, IDirectoryService directoryService = null) public BaseItem? ResolvePath(FileSystemMetadata fileInfo, Folder? parent = null, IDirectoryService? directoryService = null)
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent); => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
private BaseItem ResolvePath( private BaseItem? ResolvePath(
FileSystemMetadata fileInfo, FileSystemMetadata fileInfo,
IDirectoryService directoryService, IDirectoryService directoryService,
IItemResolver[] resolvers, IItemResolver[]? resolvers,
Folder parent = null, Folder? parent = null,
CollectionType? collectionType = null, CollectionType? collectionType = null,
LibraryOptions libraryOptions = null) LibraryOptions? libraryOptions = null)
{ {
ArgumentNullException.ThrowIfNull(fileInfo); ArgumentNullException.ThrowIfNull(fileInfo);
@ -598,7 +616,7 @@ namespace Emby.Server.Implementations.Library
return ResolveItem(args, resolvers); return ResolveItem(args, resolvers);
} }
public bool IgnoreFile(FileSystemMetadata file, BaseItem parent) public bool IgnoreFile(FileSystemMetadata file, BaseItem? parent)
=> EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)); => EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths) public List<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
@ -673,16 +691,16 @@ namespace Emby.Server.Implementations.Library
private IEnumerable<BaseItem> ResolveFileList( private IEnumerable<BaseItem> ResolveFileList(
IReadOnlyList<FileSystemMetadata> fileList, IReadOnlyList<FileSystemMetadata> fileList,
IDirectoryService directoryService, IDirectoryService directoryService,
Folder parent, Folder? parent,
CollectionType? collectionType, CollectionType? collectionType,
IItemResolver[] resolvers, IItemResolver[]? resolvers,
LibraryOptions libraryOptions) LibraryOptions libraryOptions)
{ {
// Given that fileList is a list we can save enumerator allocations by indexing // Given that fileList is a list we can save enumerator allocations by indexing
for (var i = 0; i < fileList.Count; i++) for (var i = 0; i < fileList.Count; i++)
{ {
var file = fileList[i]; var file = fileList[i];
BaseItem result = null; BaseItem? result = null;
try try
{ {
result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions); result = ResolvePath(file, directoryService, resolvers, parent, collectionType, libraryOptions);
@ -711,7 +729,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(rootFolderPath); Directory.CreateDirectory(rootFolderPath);
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath))) (ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)) as Folder ?? throw new InvalidOperationException("Something went very wong"))
.DeepCopy<Folder, AggregateFolder>(); .DeepCopy<Folder, AggregateFolder>();
// In case program data folder was moved // In case program data folder was moved
@ -777,7 +795,7 @@ namespace Emby.Server.Implementations.Library
Directory.CreateDirectory(userRootPath); Directory.CreateDirectory(userRootPath);
var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder)); var newItemId = GetNewItemId(userRootPath, typeof(UserRootFolder));
UserRootFolder tmpItem = null; UserRootFolder? tmpItem = null;
try try
{ {
tmpItem = GetItemById(newItemId) as UserRootFolder; tmpItem = GetItemById(newItemId) as UserRootFolder;
@ -790,7 +808,8 @@ namespace Emby.Server.Implementations.Library
if (tmpItem is null) if (tmpItem is null)
{ {
_logger.LogDebug("Creating new userRootFolder with DeepCopy"); _logger.LogDebug("Creating new userRootFolder with DeepCopy");
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>(); tmpItem = (ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath)) as Folder ?? throw new InvalidOperationException("Failed to get user root path"))
.DeepCopy<Folder, UserRootFolder>();
} }
// In case program data folder was moved // In case program data folder was moved
@ -809,7 +828,8 @@ namespace Emby.Server.Implementations.Library
return _userRootFolder; return _userRootFolder;
} }
public BaseItem FindByPath(string path, bool? isFolder) /// <inheritdoc />
public BaseItem? FindByPath(string path, bool? isFolder)
{ {
// If this returns multiple items it could be tricky figuring out which one is correct. // If this returns multiple items it could be tricky figuring out which one is correct.
// In most cases, the newest one will be and the others obsolete but not yet cleaned up // In most cases, the newest one will be and the others obsolete but not yet cleaned up
@ -828,12 +848,8 @@ namespace Emby.Server.Implementations.Library
.FirstOrDefault(); .FirstOrDefault();
} }
/// <summary> /// <inheritdoc />
/// Gets the person. public Person? GetPerson(string name)
/// </summary>
/// <param name="name">The name.</param>
/// <returns>Task{Person}.</returns>
public Person GetPerson(string name)
{ {
var path = Person.GetPath(name); var path = Person.GetPath(name);
var id = GetItemByNameId<Person>(path); var id = GetItemByNameId<Person>(path);
@ -1015,7 +1031,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
private async Task ValidateTopLibraryFolders(CancellationToken cancellationToken) public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
{ {
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
@ -1024,7 +1040,8 @@ namespace Emby.Server.Implementations.Library
new Progress<double>(), new Progress<double>(),
new MetadataRefreshOptions(new DirectoryService(_fileSystem)), new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false, recursive: false,
cancellationToken).ConfigureAwait(false); allowRemoveRoot: removeRoot,
cancellationToken: cancellationToken).ConfigureAwait(false);
await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false); await GetUserRootFolder().RefreshMetadata(cancellationToken).ConfigureAwait(false);
@ -1032,7 +1049,8 @@ namespace Emby.Server.Implementations.Library
new Progress<double>(), new Progress<double>(),
new MetadataRefreshOptions(new DirectoryService(_fileSystem)), new MetadataRefreshOptions(new DirectoryService(_fileSystem)),
recursive: false, recursive: false,
cancellationToken).ConfigureAwait(false); allowRemoveRoot: removeRoot,
cancellationToken: cancellationToken).ConfigureAwait(false);
// Quickly scan CollectionFolders for changes // Quickly scan CollectionFolders for changes
foreach (var folder in GetUserRootFolder().Children.OfType<Folder>()) foreach (var folder in GetUserRootFolder().Children.OfType<Folder>())
@ -1050,7 +1068,7 @@ namespace Emby.Server.Implementations.Library
var innerProgress = new Progress<double>(pct => progress.Report(pct * 0.96)); var innerProgress = new Progress<double>(pct => progress.Report(pct * 0.96));
// Validate the entire media library // Validate the entire media library
await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken).ConfigureAwait(false); await RootFolder.ValidateChildren(innerProgress, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), recursive: true, cancellationToken: cancellationToken).ConfigureAwait(false);
progress.Report(96); progress.Report(96);
@ -1140,7 +1158,7 @@ namespace Emby.Server.Implementations.Library
.ToList(); .ToList();
} }
private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders, HashSet<Guid> refreshQueue) private VirtualFolderInfo GetVirtualFolderInfo(string dir, List<BaseItem> allCollectionFolders, HashSet<Guid>? refreshQueue)
{ {
var info = new VirtualFolderInfo var info = new VirtualFolderInfo
{ {
@ -1204,20 +1222,15 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
/// <summary> /// <inheritdoc />
/// Gets the item by id. public BaseItem? GetItemById(Guid id)
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
public BaseItem GetItemById(Guid id)
{ {
if (id.IsEmpty()) if (id.IsEmpty())
{ {
throw new ArgumentException("Guid can't be empty", nameof(id)); throw new ArgumentException("Guid can't be empty", nameof(id));
} }
if (_cache.TryGetValue(id, out BaseItem item)) if (_cache.TryGetValue(id, out BaseItem? item))
{ {
return item; return item;
} }
@ -1233,7 +1246,7 @@ namespace Emby.Server.Implementations.Library
} }
/// <inheritdoc /> /// <inheritdoc />
public T GetItemById<T>(Guid id) public T? GetItemById<T>(Guid id)
where T : BaseItem where T : BaseItem
{ {
var item = GetItemById(id); var item = GetItemById(id);
@ -1245,6 +1258,22 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
/// <inheritdoc />
public T? GetItemById<T>(Guid id, Guid userId)
where T : BaseItem
{
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
return GetItemById<T>(id, user);
}
/// <inheritdoc />
public T? GetItemById<T>(Guid id, User? user)
where T : BaseItem
{
var item = GetItemById<T>(id);
return ItemIsVisible(item, user) ? item : null;
}
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{ {
if (query.Recursive && !query.ParentId.IsEmpty()) if (query.Recursive && !query.ParentId.IsEmpty())
@ -1405,7 +1434,7 @@ namespace Emby.Server.Implementations.Library
var parents = new BaseItem[len]; var parents = new BaseItem[len];
for (int i = 0; i < len; i++) for (int i = 0; i < len; i++)
{ {
parents[i] = GetItemById(ancestorIds[i]); parents[i] = GetItemById(ancestorIds[i]) ?? throw new ArgumentException($"Failed to find parent with id: {ancestorIds[i]}");
if (parents[i] is not (ICollectionFolder or UserView)) if (parents[i] is not (ICollectionFolder or UserView))
{ {
return; return;
@ -1419,7 +1448,7 @@ namespace Emby.Server.Implementations.Library
// Prevent searching in all libraries due to empty filter // Prevent searching in all libraries due to empty filter
if (query.TopParentIds.Length == 0) if (query.TopParentIds.Length == 0)
{ {
query.TopParentIds = new[] { Guid.NewGuid() }; query.TopParentIds = [Guid.NewGuid()];
} }
} }
@ -1516,7 +1545,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User user) private IEnumerable<Guid> GetTopParentIdsForQuery(BaseItem item, User? user)
{ {
if (item is UserView view) if (item is UserView view)
{ {
@ -1585,16 +1614,20 @@ namespace Emby.Server.Implementations.Library
/// <returns>IEnumerable{System.String}.</returns> /// <returns>IEnumerable{System.String}.</returns>
public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user) public async Task<IEnumerable<Video>> GetIntros(BaseItem item, User user)
{ {
if (IntroProviders.Length == 0)
{
return [];
}
var tasks = IntroProviders var tasks = IntroProviders
.Take(1)
.Select(i => GetIntros(i, item, user)); .Select(i => GetIntros(i, item, user));
var items = await Task.WhenAll(tasks).ConfigureAwait(false); var items = await Task.WhenAll(tasks).ConfigureAwait(false);
return items return items
.SelectMany(i => i.ToArray()) .SelectMany(i => i)
.Select(ResolveIntro) .Select(ResolveIntro)
.Where(i => i is not null); .Where(i => i is not null)!; // null values got filtered out
} }
/// <summary> /// <summary>
@ -1623,9 +1656,9 @@ namespace Emby.Server.Implementations.Library
/// </summary> /// </summary>
/// <param name="info">The info.</param> /// <param name="info">The info.</param>
/// <returns>Video.</returns> /// <returns>Video.</returns>
private Video ResolveIntro(IntroInfo info) private Video? ResolveIntro(IntroInfo info)
{ {
Video video = null; Video? video = null;
if (info.ItemId.HasValue) if (info.ItemId.HasValue)
{ {
@ -1676,42 +1709,42 @@ namespace Emby.Server.Implementations.Library
return video; return video;
} }
/// <summary> /// <inheritdoc />
/// Sorts the specified sort by. public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder)
/// </summary>
/// <param name="items">The items.</param>
/// <param name="user">The user.</param>
/// <param name="sortBy">The sort by.</param>
/// <param name="sortOrder">The sort order.</param>
/// <returns>IEnumerable{BaseItem}.</returns>
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder)
{ {
var isFirst = true; IOrderedEnumerable<BaseItem>? orderedItems = null;
IOrderedEnumerable<BaseItem> orderedItems = null;
foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c is not null)) foreach (var orderBy in sortBy.Select(o => GetComparer(o, user)).Where(c => c is not null))
{ {
if (isFirst) if (orderBy is RandomComparer)
{ {
orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, orderBy) : items.OrderBy(i => i, orderBy); var randomItems = items.ToArray();
Random.Shared.Shuffle(randomItems);
items = randomItems;
// Items are no longer ordered at this point, so set orderedItems back to null
orderedItems = null;
}
else if (orderedItems is null)
{
orderedItems = sortOrder == SortOrder.Descending
? items.OrderByDescending(i => i, orderBy)
: items.OrderBy(i => i, orderBy);
} }
else else
{ {
orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, orderBy) : orderedItems.ThenBy(i => i, orderBy); orderedItems = sortOrder == SortOrder.Descending
? orderedItems!.ThenByDescending(i => i, orderBy)
: orderedItems!.ThenBy(i => i, orderBy); // orderedItems is set during the first iteration
} }
isFirst = false;
} }
return orderedItems ?? items; return orderedItems ?? items;
} }
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy) /// <inheritdoc />
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy)
{ {
var isFirst = true; IOrderedEnumerable<BaseItem>? orderedItems = null;
IOrderedEnumerable<BaseItem> orderedItems = null;
foreach (var (name, sortOrder) in orderBy) foreach (var (name, sortOrder) in orderBy)
{ {
@ -1721,16 +1754,26 @@ namespace Emby.Server.Implementations.Library
continue; continue;
} }
if (isFirst) if (comparer is RandomComparer)
{ {
orderedItems = sortOrder == SortOrder.Descending ? items.OrderByDescending(i => i, comparer) : items.OrderBy(i => i, comparer); var randomItems = items.ToArray();
Random.Shared.Shuffle(randomItems);
items = randomItems;
// Items are no longer ordered at this point, so set orderedItems back to null
orderedItems = null;
}
else if (orderedItems is null)
{
orderedItems = sortOrder == SortOrder.Descending
? items.OrderByDescending(i => i, comparer)
: items.OrderBy(i => i, comparer);
} }
else else
{ {
orderedItems = sortOrder == SortOrder.Descending ? orderedItems.ThenByDescending(i => i, comparer) : orderedItems.ThenBy(i => i, comparer); orderedItems = sortOrder == SortOrder.Descending
? orderedItems!.ThenByDescending(i => i, comparer)
: orderedItems!.ThenBy(i => i, comparer); // orderedItems is set during the first iteration
} }
isFirst = false;
} }
return orderedItems ?? items; return orderedItems ?? items;
@ -1742,14 +1785,14 @@ namespace Emby.Server.Implementations.Library
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <returns>IBaseItemComparer.</returns> /// <returns>IBaseItemComparer.</returns>
private IBaseItemComparer GetComparer(ItemSortBy name, User user) private IBaseItemComparer? GetComparer(ItemSortBy name, User? user)
{ {
var comparer = Comparers.FirstOrDefault(c => name == c.Type); var comparer = Comparers.FirstOrDefault(c => name == c.Type);
// If it requires a user, create a new one, and assign the user // If it requires a user, create a new one, and assign the user
if (comparer is IUserBaseItemComparer) if (comparer is IUserBaseItemComparer)
{ {
var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType()); var userComparer = (IUserBaseItemComparer)Activator.CreateInstance(comparer.GetType())!; // only null for Nullable<T> instances
userComparer.User = user; userComparer.User = user;
userComparer.UserManager = _userManager; userComparer.UserManager = _userManager;
@ -1761,23 +1804,14 @@ namespace Emby.Server.Implementations.Library
return comparer; return comparer;
} }
/// <summary> /// <inheritdoc />
/// Creates the item. public void CreateItem(BaseItem item, BaseItem? parent)
/// </summary>
/// <param name="item">The item.</param>
/// <param name="parent">The parent item.</param>
public void CreateItem(BaseItem item, BaseItem parent)
{ {
CreateItems(new[] { item }, parent, CancellationToken.None); CreateItems(new[] { item }, parent, CancellationToken.None);
} }
/// <summary> /// <inheritdoc />
/// Creates the items. public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
/// </summary>
/// <param name="items">The items.</param>
/// <param name="parent">The parent item.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem parent, CancellationToken cancellationToken)
{ {
_itemRepository.SaveItems(items, cancellationToken); _itemRepository.SaveItems(items, cancellationToken);
@ -1860,7 +1894,7 @@ namespace Emby.Server.Implementations.Library
try try
{ {
var index = item.GetImageIndex(img); var index = item.GetImageIndex(img);
image = await ConvertImageToLocal(item, img, index, removeOnFailure: true).ConfigureAwait(false); image = await ConvertImageToLocal(item, img, index, true).ConfigureAwait(false);
} }
catch (ArgumentException) catch (ArgumentException)
{ {
@ -2059,16 +2093,16 @@ namespace Emby.Server.Implementations.Library
public LibraryOptions GetLibraryOptions(BaseItem item) public LibraryOptions GetLibraryOptions(BaseItem item)
{ {
if (item is not CollectionFolder collectionFolder) if (item is CollectionFolder collectionFolder)
{ {
// List.Find is more performant than FirstOrDefault due to enumerator allocation return collectionFolder.GetLibraryOptions();
collectionFolder = GetCollectionFolders(item)
.Find(folder => folder is CollectionFolder) as CollectionFolder;
} }
return collectionFolder is null // List.Find is more performant than FirstOrDefault due to enumerator allocation
? new LibraryOptions() return GetCollectionFolders(item)
: collectionFolder.GetLibraryOptions(); .Find(folder => folder is CollectionFolder) is CollectionFolder collectionFolder2
? collectionFolder2.GetLibraryOptions()
: new LibraryOptions();
} }
public CollectionType? GetContentType(BaseItem item) public CollectionType? GetContentType(BaseItem item)
@ -2422,7 +2456,7 @@ namespace Emby.Server.Implementations.Library
{ {
if (parentId.HasValue) if (parentId.HasValue)
{ {
return GetItemById(parentId.Value); return GetItemById(parentId.Value) ?? throw new ArgumentException($"Invalid parent id: {parentId.Value}");
} }
if (!userId.IsNullOrEmpty()) if (!userId.IsNullOrEmpty())
@ -2459,7 +2493,7 @@ namespace Emby.Server.Implementations.Library
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd; var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
// TODO nullable - what are we trying to do there with empty episodeInfo? // TODO nullable - what are we trying to do there with empty episodeInfo?
EpisodeInfo episodeInfo = null; EpisodeInfo? episodeInfo = null;
if (episode.IsFileProtocol) if (episode.IsFileProtocol)
{ {
episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming); episodeInfo = resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming);
@ -2662,7 +2696,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType) BaseItem? GetExtra(FileSystemMetadata file, ExtraType extraType)
{ {
var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType)); var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
if (extra is not Video && extra is not Audio) if (extra is not Video && extra is not Audio)
@ -2677,16 +2711,21 @@ namespace Emby.Server.Implementations.Library
extra = itemById; extra = itemById;
} }
extra.ExtraType = extraType; // Only update extra type if it is more specific then the currently known extra type
if (extra.ExtraType is null or ExtraType.Unknown || extraType != ExtraType.Unknown)
{
extra.ExtraType = extraType;
}
extra.ParentId = Guid.Empty; extra.ParentId = Guid.Empty;
extra.OwnerId = owner.Id; extra.OwnerId = owner.Id;
return extra; return extra;
} }
} }
public string GetPathAfterNetworkSubstitution(string path, BaseItem ownerItem) public string GetPathAfterNetworkSubstitution(string path, BaseItem? ownerItem)
{ {
string newPath; string? newPath;
if (ownerItem is not null) if (ownerItem is not null)
{ {
var libraryOptions = GetLibraryOptions(ownerItem); var libraryOptions = GetLibraryOptions(ownerItem);
@ -2760,8 +2799,8 @@ namespace Emby.Server.Implementations.Library
} }
}) })
.Where(i => i is not null) .Where(i => i is not null)
.Where(i => query.User is null || i.IsVisible(query.User)) .Where(i => query.User is null || i!.IsVisible(query.User))
.ToList(); .ToList()!; // null values are filtered out
} }
public List<string> GetPeopleNames(InternalPeopleQuery query) public List<string> GetPeopleNames(InternalPeopleQuery query)
@ -2783,8 +2822,10 @@ namespace Emby.Server.Implementations.Library
} }
_itemRepository.UpdatePeople(item.Id, people); _itemRepository.UpdatePeople(item.Id, people);
if (people is not null)
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false); {
await SavePeopleMetadataAsync(people, cancellationToken).ConfigureAwait(false);
}
} }
public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex, bool removeOnFailure) public async Task<ItemImageInfo> ConvertImageToLocal(BaseItem item, ItemImageInfo image, int imageIndex, bool removeOnFailure)
@ -2863,7 +2904,7 @@ namespace Emby.Server.Implementations.Library
if (collectionType is not null) if (collectionType is not null)
{ {
var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection"); var path = Path.Combine(virtualFolderPath, collectionType.ToString()!.ToLowerInvariant() + ".collection"); // Can't be null with legal values?
await File.WriteAllBytesAsync(path, Array.Empty<byte>()).ConfigureAwait(false); await File.WriteAllBytesAsync(path, Array.Empty<byte>()).ConfigureAwait(false);
} }
@ -2897,7 +2938,7 @@ namespace Emby.Server.Implementations.Library
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken) private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{ {
List<BaseItem> personsToSave = null; List<BaseItem>? personsToSave = null;
foreach (var person in people) foreach (var person in people)
{ {
@ -3010,9 +3051,7 @@ namespace Emby.Server.Implementations.Library
{ {
var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath); var libraryOptions = CollectionFolder.GetLibraryOptions(virtualFolderPath);
var list = libraryOptions.PathInfos.ToList(); libraryOptions.PathInfos = [..libraryOptions.PathInfos, pathInfo];
list.Add(pathInfo);
libraryOptions.PathInfos = list.ToArray();
SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
@ -3031,8 +3070,7 @@ namespace Emby.Server.Implementations.Library
SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions); SyncLibraryOptionsToLocations(virtualFolderPath, libraryOptions);
var list = libraryOptions.PathInfos.ToList(); foreach (var originalPathInfo in libraryOptions.PathInfos)
foreach (var originalPathInfo in list)
{ {
if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal)) if (string.Equals(mediaPath.Path, originalPathInfo.Path, StringComparison.Ordinal))
{ {
@ -3041,8 +3079,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
libraryOptions.PathInfos = list.ToArray();
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
} }
@ -3095,7 +3131,7 @@ namespace Emby.Server.Implementations.Library
if (refreshLibrary) if (refreshLibrary)
{ {
await ValidateTopLibraryFolders(CancellationToken.None).ConfigureAwait(false); await ValidateTopLibraryFolders(CancellationToken.None, true).ConfigureAwait(false);
StartScanInBackground(); StartScanInBackground();
} }
@ -3115,7 +3151,7 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(path)); throw new ArgumentNullException(nameof(path));
} }
List<NameValuePair> removeList = null; List<NameValuePair>? removeList = null;
foreach (var contentType in _configurationManager.Configuration.ContentTypes) foreach (var contentType in _configurationManager.Configuration.ContentTypes)
{ {
@ -3168,5 +3204,20 @@ namespace Emby.Server.Implementations.Library
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
} }
private static bool ItemIsVisible(BaseItem? item, User? user)
{
if (item is null)
{
return false;
}
if (user is null)
{
return true;
}
return item is UserRootFolder || item.IsVisibleStandalone(user);
}
} }
} }

View File

@ -113,6 +113,11 @@ namespace Emby.Server.Implementations.Library
return true; return true;
} }
if (stream.IsPgsSubtitleStream)
{
return true;
}
return false; return false;
} }
@ -191,7 +196,7 @@ namespace Emby.Server.Implementations.Library
if (user is not null) if (user is not null)
{ {
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
if (item.MediaType == MediaType.Audio) if (item.MediaType == MediaType.Audio)
{ {
@ -274,7 +279,7 @@ namespace Emby.Server.Implementations.Library
var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken)); var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false); var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results.SelectMany(i => i.ToList()); return results.SelectMany(i => i);
} }
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken) private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(BaseItem item, IMediaSourceProvider provider, CancellationToken cancellationToken)
@ -296,7 +301,7 @@ namespace Emby.Server.Implementations.Library
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error getting media sources"); _logger.LogError(ex, "Error getting media sources");
return Enumerable.Empty<MediaSourceInfo>(); return [];
} }
} }
@ -339,7 +344,7 @@ namespace Emby.Server.Implementations.Library
{ {
foreach (var source in sources) foreach (var source in sources)
{ {
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user); SetDefaultAudioAndSubtitleStreamIndices(item, source, user);
if (item.MediaType == MediaType.Audio) if (item.MediaType == MediaType.Audio)
{ {
@ -360,7 +365,7 @@ namespace Emby.Server.Implementations.Library
{ {
if (string.IsNullOrEmpty(language)) if (string.IsNullOrEmpty(language))
{ {
return Array.Empty<string>(); return [];
} }
var culture = _localizationManager.FindLanguageInfo(language); var culture = _localizationManager.FindLanguageInfo(language);
@ -369,14 +374,15 @@ namespace Emby.Server.Implementations.Library
return culture.ThreeLetterISOLanguageNames; return culture.ThreeLetterISOLanguageNames;
} }
return new string[] { language }; return [language];
} }
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{ {
if (userData.SubtitleStreamIndex.HasValue if (userData.SubtitleStreamIndex.HasValue
&& user.RememberSubtitleSelections && user.RememberSubtitleSelections
&& user.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection) && user.SubtitleMode != SubtitlePlaybackMode.None
&& allowRememberingSelection)
{ {
var index = userData.SubtitleStreamIndex.Value; var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid // Make sure the saved index is still valid
@ -390,7 +396,7 @@ namespace Emby.Server.Implementations.Library
var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference); var preferredSubs = NormalizeLanguage(user.SubtitleLanguagePreference);
var defaultAudioIndex = source.DefaultAudioStreamIndex; var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex is null var audioLanguage = defaultAudioIndex is null
? null ? null
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault(); : source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
@ -398,9 +404,9 @@ namespace Emby.Server.Implementations.Library
source.MediaStreams, source.MediaStreams,
preferredSubs, preferredSubs,
user.SubtitleMode, user.SubtitleMode,
audioLangage); audioLanguage);
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLangage); MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage);
} }
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection) private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
@ -421,7 +427,7 @@ namespace Emby.Server.Implementations.Library
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
} }
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user) public void SetDefaultAudioAndSubtitleStreamIndices(BaseItem item, MediaSourceInfo source, User user)
{ {
// Item would only be null if the app didn't supply ItemId as part of the live stream open request // Item would only be null if the app didn't supply ItemId as part of the live stream open request
var mediaType = item?.MediaType ?? MediaType.Video; var mediaType = item?.MediaType ?? MediaType.Video;
@ -526,7 +532,7 @@ namespace Emby.Server.Implementations.Library
var item = request.ItemId.IsEmpty() var item = request.ItemId.IsEmpty()
? null ? null
: _libraryManager.GetItemById(request.ItemId); : _libraryManager.GetItemById(request.ItemId);
SetDefaultAudioAndSubtitleStreamIndexes(item, clone, user); SetDefaultAudioAndSubtitleStreamIndices(item, clone, user);
} }
return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider); return new Tuple<LiveStreamResponse, IDirectStreamProvider>(new LiveStreamResponse(clone), liveStream as IDirectStreamProvider);

View File

@ -124,16 +124,16 @@ namespace Emby.Server.Implementations.Library
} }
else if (mode == SubtitlePlaybackMode.Always) else if (mode == SubtitlePlaybackMode.Always)
{ {
// always load the most suitable full subtitles // Always load the most suitable full subtitles
filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList(); filteredStreams = sortedStreams.Where(s => !s.IsForced).ToList();
} }
else if (mode == SubtitlePlaybackMode.OnlyForced) else if (mode == SubtitlePlaybackMode.OnlyForced)
{ {
// always load the most suitable full subtitles // Always load the most suitable full subtitles
filteredStreams = sortedStreams.Where(s => s.IsForced).ToList(); filteredStreams = sortedStreams.Where(s => s.IsForced).ToList();
} }
// load forced subs if we have found no suitable full subtitles // Load forced subs if we have found no suitable full subtitles
var iterStreams = filteredStreams is null || filteredStreams.Count == 0 var iterStreams = filteredStreams is null || filteredStreams.Count == 0
? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase)) ? sortedStreams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase))
: filteredStreams; : filteredStreams;

View File

@ -31,8 +31,9 @@ namespace Emby.Server.Implementations.Library
var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase); var attributeIndex = str.IndexOf(attribute, StringComparison.OrdinalIgnoreCase);
// Must be at least 3 characters after the attribute =, ], any character. // Must be at least 3 characters after the attribute =, ], any character,
var maxIndex = str.Length - attribute.Length - 3; // then we offset it by 1, because we want the index and not length.
var maxIndex = str.Length - attribute.Length - 2;
while (attributeIndex > -1 && attributeIndex < maxIndex) while (attributeIndex > -1 && attributeIndex < maxIndex)
{ {
var attributeEnd = attributeIndex + attribute.Length; var attributeEnd = attributeIndex + attribute.Length;

View File

@ -35,11 +35,11 @@ namespace Emby.Server.Implementations.Library
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) ||
item.GetParents().Any(i => i.IsLocked); item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values // Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path); var fileInfo = directoryService.GetFileSystemEntry(item.Path);
if (fileInfo is null) if (fileInfo is null)
{ {
return false; return false;

View File

@ -13,7 +13,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; 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.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Audio;
using Emby.Naming.Common; using Emby.Naming.Common;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -85,6 +86,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
} }
var albumResolver = new MusicAlbumResolver(_logger, _namingOptions, _directoryService); var albumResolver = new MusicAlbumResolver(_logger, _namingOptions, _directoryService);
var albumParser = new AlbumParser(_namingOptions);
var directories = args.FileSystemChildren.Where(i => i.IsDirectory); var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
@ -100,6 +102,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
} }
} }
// If the folder is a multi-disc folder, then it is not an artist folder
if (albumParser.IsMultiPart(fileSystemInfo.FullName))
{
return;
}
// If we contain a music album assume we are an artist folder // If we contain a music album assume we are an artist folder
if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, _directoryService)) if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, _directoryService))
{ {

View File

@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return null; return null;
} }
if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) if (filename.Contains("[boxset]", StringComparison.OrdinalIgnoreCase) || args.ContainsFileSystemEntryByName("collection.xml"))
{ {
return new BoxSet return new BoxSet
{ {

View File

@ -1,7 +1,5 @@
#nullable disable #nullable disable
#pragma warning disable CS1591
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -11,7 +9,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Resolvers namespace Emby.Server.Implementations.Library.Resolvers
{ {
@ -20,11 +17,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
/// </summary> /// </summary>
public class PlaylistResolver : GenericFolderResolver<Playlist> public class PlaylistResolver : GenericFolderResolver<Playlist>
{ {
private CollectionType?[] _musicPlaylistCollectionTypes = private readonly CollectionType?[] _musicPlaylistCollectionTypes =
{ [
null, null,
CollectionType.music CollectionType.music
}; ];
/// <inheritdoc/> /// <inheritdoc/>
protected override Playlist Resolve(ItemResolveArgs args) protected override Playlist Resolve(ItemResolveArgs args)

View File

@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
{ {
IndexNumber = seasonParserResult.SeasonNumber, IndexNumber = seasonParserResult.SeasonNumber,
SeriesId = series.Id, SeriesId = series.Id,
SeriesName = series.Name SeriesName = series.Name,
Path = seasonParserResult.IsSeasonFolder ? path : null
}; };
if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder) if (!season.IndexNumber.HasValue || !seasonParserResult.IsSeasonFolder)
@ -78,27 +79,16 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
} }
} }
if (season.IndexNumber.HasValue) if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name))
{ {
var seasonNumber = season.IndexNumber.Value; var seasonNumber = season.IndexNumber.Value;
if (string.IsNullOrEmpty(season.Name)) season.Name = seasonNumber == 0 ?
{ args.LibraryOptions.SeasonZeroDisplayName :
var seasonNames = series.SeasonNames; string.Format(
if (seasonNames.TryGetValue(seasonNumber, out var seasonName)) CultureInfo.InvariantCulture,
{ _localization.GetLocalizedString("NameSeasonNumber"),
season.Name = seasonName; seasonNumber,
} args.LibraryOptions.PreferredMetadataLanguage);
else
{
season.Name = seasonNumber == 0 ?
args.LibraryOptions.SeasonZeroDisplayName :
string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("NameSeasonNumber"),
seasonNumber,
args.LibraryOptions.PreferredMetadataLanguage);
}
}
} }
return season; return season;

View File

@ -303,8 +303,8 @@ namespace Emby.Server.Implementations.Library
{ {
// Handle situations with the grouping setting, e.g. movies showing up in tv, etc. // Handle situations with the grouping setting, e.g. movies showing up in tv, etc.
// Thanks to mixed content libraries included in the UserView // Thanks to mixed content libraries included in the UserView
var hasCollectionType = parents.OfType<UserView>().ToArray(); var hasCollectionType = parents.OfType<UserView>().ToList();
if (hasCollectionType.Length > 0) if (hasCollectionType.Count > 0)
{ {
if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies))
{ {

View File

@ -64,6 +64,11 @@ namespace Emby.Server.Implementations.Library.Validators
try try
{ {
var item = _libraryManager.GetPerson(person); var item = _libraryManager.GetPerson(person);
if (item is null)
{
_logger.LogWarning("Failed to get person: {Name}", person);
continue;
}
var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem)) var options = new MetadataRefreshOptions(new DirectoryService(_fileSystem))
{ {
@ -92,7 +97,7 @@ namespace Emby.Server.Implementations.Library.Validators
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = new[] { BaseItemKind.Person }, IncludeItemTypes = [BaseItemKind.Person],
IsDeadPerson = true, IsDeadPerson = true,
IsLocked = false IsLocked = false
}); });

View File

@ -1 +1,3 @@
{} {
"Albums": "аальбомқәа"
}

View File

@ -5,12 +5,12 @@
"Favorites": "Gunstelinge", "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings", "HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}", "ValueSpecialEpisodeName": "Spesiale - {0}",
"HeaderAlbumArtists": "Kunstenaars se Album", "HeaderAlbumArtists": "Album kunstenaars",
"Books": "Boeke", "Books": "Boeke",
"HeaderNextUp": "Volgende", "HeaderNextUp": "Volgende",
"Movies": "Flieks", "Movies": "Flieks",
"Shows": "Televisie Reekse", "Shows": "Televisie Reekse",
"HeaderContinueWatching": "Kyk Verder", "HeaderContinueWatching": "Hou aan kyk",
"HeaderFavoriteEpisodes": "Gunsteling Episodes", "HeaderFavoriteEpisodes": "Gunsteling Episodes",
"Photos": "Foto's", "Photos": "Foto's",
"Playlists": "Snitlyste", "Playlists": "Snitlyste",
@ -19,7 +19,7 @@
"Sync": "Sinkroniseer", "Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies", "HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies", "Songs": "Liedjies",
"DeviceOnlineWithName": "{0} is gekoppel", "DeviceOnlineWithName": "{0} is aanlyn",
"DeviceOfflineWithName": "{0} is ontkoppel", "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings", "Collections": "Versamelings",
"Inherit": "Ontvang", "Inherit": "Ontvang",
@ -61,7 +61,7 @@
"NotificationOptionPluginInstalled": "Inprop module geïnstalleer", "NotificationOptionPluginInstalled": "Inprop module geïnstalleer",
"NotificationOptionPluginError": "Inprop module het misluk", "NotificationOptionPluginError": "Inprop module het misluk",
"NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg", "NotificationOptionNewLibraryContent": "Nuwe inhoud bygevoeg",
"NotificationOptionInstallationFailed": "Installering het misluk", "NotificationOptionInstallationFailed": "Installasie mislukking",
"NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai", "NotificationOptionCameraImageUploaded": "Kamera foto is opgelaai",
"NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop", "NotificationOptionAudioPlaybackStopped": "Oudio terugspeel het gestop",
"NotificationOptionAudioPlayback": "Oudio terugspeel het begin", "NotificationOptionAudioPlayback": "Oudio terugspeel het begin",
@ -86,9 +86,9 @@
"HomeVideos": "Tuis Videos", "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames", "HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres", "Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
"ChapterNameValue": "Hoofstuk {0}", "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}", "CameraImageUploadedFrom": "'n Nuwe kamera foto is opgelaai vanaf {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums", "Albums": "Albums",
"TasksChannelsCategory": "Internet kanale", "TasksChannelsCategory": "Internet kanale",
@ -114,8 +114,8 @@
"TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.", "TaskRefreshChapterImagesDescription": "Maak kleinkiekeis (fotos) vir films wat hoofstukke het.",
"TaskRefreshChapterImages": "Verkry Hoofstuk Beelde", "TaskRefreshChapterImages": "Verkry Hoofstuk Beelde",
"Undefined": "Ongedefineerd", "Undefined": "Ongedefineerd",
"Forced": "Geforseer", "Forced": "Geforseerd",
"Default": "Oorspronklik", "Default": "Standaard",
"TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.", "TaskCleanActivityLogDescription": "Verwyder aktiwiteitsaantekeninge ouer as die opgestelde ouderdom.",
"TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon", "TaskCleanActivityLog": "Maak Aktiwiteitsaantekeninge Skoon",
"TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.", "TaskOptimizeDatabaseDescription": "Komprimeer databasis en verkort vrye ruimte. As hierdie taak uitgevoer word nadat die media versameling geskandeer is of ander veranderings aangebring is wat databasisaanpassings impliseer, kan dit die prestasie verbeter.",
@ -125,5 +125,9 @@
"External": "Ekstern", "External": "Ekstern",
"HearingImpaired": "gehoorgestremd", "HearingImpaired": "gehoorgestremd",
"TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde", "TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde",
"TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling." "TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling.",
"TaskAudioNormalizationDescription": "Skandeer lêers vir oudio-normaliseringsdata.",
"TaskAudioNormalization": "Odio Normalisering",
"TaskCleanCollectionsAndPlaylists": "Maak versamelings en snitlyste skoon",
"TaskCleanCollectionsAndPlaylistsDescription": "Verwyder items uit versamelings en snitlyste wat nie meer bestaan nie."
} }

View File

@ -11,7 +11,7 @@
"Collections": "التجميعات", "Collections": "التجميعات",
"DeviceOfflineWithName": "قُطِع الاتصال ب{0}", "DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل", "DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فشلت من {0}", "FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فاشلة من {0}",
"Favorites": "المفضلة", "Favorites": "المفضلة",
"Folders": "المجلدات", "Folders": "المجلدات",
"Genres": "التصنيفات", "Genres": "التصنيفات",
@ -126,5 +126,9 @@
"External": "خارجي", "External": "خارجي",
"HearingImpaired": "ضعاف السمع", "HearingImpaired": "ضعاف السمع",
"TaskRefreshTrickplayImages": "توليد صور Trickplay", "TaskRefreshTrickplayImages": "توليد صور Trickplay",
"TaskRefreshTrickplayImagesDescription": "يُنشئ معاينات Trickplay لمقاطع الفيديو في المكتبات المُمكّنة." "TaskRefreshTrickplayImagesDescription": "يُنشئ معاينات Trickplay لمقاطع الفيديو في المكتبات المُمكّنة.",
"TaskCleanCollectionsAndPlaylists": "حذف المجموعات وقوائم التشغيل",
"TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.",
"TaskAudioNormalization": "تطبيع الصوت",
"TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت."
} }

View File

@ -52,7 +52,7 @@
"UserDownloadingItemWithValues": "{0} спампоўваецца {1}", "UserDownloadingItemWithValues": "{0} спампоўваецца {1}",
"TaskOptimizeDatabase": "Аптымізаваць базу дадзеных", "TaskOptimizeDatabase": "Аптымізаваць базу дадзеных",
"Artists": "Выканаўцы", "Artists": "Выканаўцы",
"UserOfflineFromDevice": "{0} адключыўся ад {1}", "UserOfflineFromDevice": "{0} адлучыўся ад {1}",
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}", "UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
"TaskCleanActivityLogDescription": "Выдаляе старэйшыя за зададзены ўзрост запісы ў журнале актыўнасці.", "TaskCleanActivityLogDescription": "Выдаляе старэйшыя за зададзены ўзрост запісы ў журнале актыўнасці.",
"TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.", "TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.",
@ -66,7 +66,7 @@
"AppDeviceValues": "Прыкладанне: {0}, Прылада: {1}", "AppDeviceValues": "Прыкладанне: {0}, Прылада: {1}",
"Books": "Кнігі", "Books": "Кнігі",
"CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}", "CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}",
"DeviceOfflineWithName": "{0} адключыўся", "DeviceOfflineWithName": "{0} адлучыўся",
"DeviceOnlineWithName": "{0} падлучаны", "DeviceOnlineWithName": "{0} падлучаны",
"Forced": "Прымусова", "Forced": "Прымусова",
"HeaderRecordingGroups": "Групы запісаў", "HeaderRecordingGroups": "Групы запісаў",
@ -125,5 +125,9 @@
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры", "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.", "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.",
"TaskRefreshTrickplayImages": "Стварыце выявы Trickplay", "TaskRefreshTrickplayImages": "Стварыце выявы Trickplay",
"TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках." "TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках.",
"TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і спісы прайгравання",
"TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і спісаў прайгравання, якія больш не існуюць.",
"TaskAudioNormalizationDescription": "Сканіруе файлы на прадмет нармалізацыі гуку.",
"TaskAudioNormalization": "Нармалізацыя гуку"
} }

View File

@ -126,5 +126,9 @@
"External": "Extern", "External": "Extern",
"HearingImpaired": "Discapacitat auditiva", "HearingImpaired": "Discapacitat auditiva",
"TaskRefreshTrickplayImages": "Generar miniatures de línia de temps", "TaskRefreshTrickplayImages": "Generar miniatures de línia de temps",
"TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades." "TaskRefreshTrickplayImagesDescription": "Crear miniatures de línia de temps per vídeos en les biblioteques habilitades.",
"TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.",
"TaskCleanCollectionsAndPlaylists": "Neteja col·leccions i llistes de reproducció",
"TaskAudioNormalization": "Normalització d'Àudio",
"TaskAudioNormalizationDescription": "Escaneja arxius per dades de normalització d'àudio."
} }

View File

@ -22,7 +22,7 @@
"HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteEpisodes": "Oblíbené epizody",
"HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba", "HeaderFavoriteSongs": "Oblíbená hudba",
"HeaderLiveTV": "Živý přenos", "HeaderLiveTV": "TV vysílání",
"HeaderNextUp": "Další díly", "HeaderNextUp": "Další díly",
"HeaderRecordingGroups": "Skupiny nahrávek", "HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domácí videa", "HomeVideos": "Domácí videa",
@ -126,5 +126,9 @@
"External": "Externí", "External": "Externí",
"HearingImpaired": "Sluchově postižení", "HearingImpaired": "Sluchově postižení",
"TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay", "TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay",
"TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno." "TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno.",
"TaskCleanCollectionsAndPlaylists": "Pročistit kolekce a seznamy přehrávání",
"TaskCleanCollectionsAndPlaylistsDescription": "Odstraní neexistující položky z kolekcí a seznamů přehrávání.",
"TaskAudioNormalization": "Normalizace zvuku",
"TaskAudioNormalizationDescription": "Skenovat soubory za účelem normalizace zvuku."
} }

View File

@ -17,7 +17,7 @@
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumkunstnere", "HeaderAlbumArtists": "Albumkunstnere",
"HeaderContinueWatching": "Fortsæt afspilning", "HeaderContinueWatching": "Fortsæt afspilning",
"HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritkunstnere", "HeaderFavoriteArtists": "Favoritkunstnere",
"HeaderFavoriteEpisodes": "Yndlingsafsnit", "HeaderFavoriteEpisodes": "Yndlingsafsnit",
"HeaderFavoriteShows": "Yndlingsserier", "HeaderFavoriteShows": "Yndlingsserier",
@ -87,21 +87,21 @@
"UserOnlineFromDevice": "{0} er online fra {1}", "UserOnlineFromDevice": "{0} er online fra {1}",
"UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}", "UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}",
"UserPolicyUpdatedWithName": "Brugerpolitikken er blevet opdateret for {0}", "UserPolicyUpdatedWithName": "Brugerpolitikken er blevet opdateret for {0}",
"UserStartedPlayingItemWithValues": "{0} har påbegyndt afspilning af {1}", "UserStartedPlayingItemWithValues": "{0} afspiller {1} på {2}",
"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-konfigurationen.", "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata-konfigurationen.",
"TaskDownloadMissingSubtitles": "Hent manglende undertekster", "TaskDownloadMissingSubtitles": "Hent manglende undertekster",
"TaskUpdatePluginsDescription": "Henter og installerer opdateringer for plugins, som er indstillet til at blive opdateret automatisk.", "TaskUpdatePluginsDescription": "Henter og installerer opdateringer for plugins, som er konfigurerede til at blive opdateret automatisk.",
"TaskUpdatePlugins": "Opdater Plugins", "TaskUpdatePlugins": "Opdater Plugins",
"TaskCleanLogsDescription": "Sletter log-filer som er mere end {0} dage gamle.", "TaskCleanLogsDescription": "Sletter log-filer som er mere end {0} dage gamle.",
"TaskCleanLogs": "Ryd Log-mappe", "TaskCleanLogs": "Ryd Log-mappe",
"TaskRefreshLibraryDescription": "Scanner dit mediebibliotek for nye filer og opdateret metadata.", "TaskRefreshLibraryDescription": "Scanner dit mediebibliotek for nye filer og opdateret metadata.",
"TaskRefreshLibrary": "Scan Mediebibliotek", "TaskRefreshLibrary": "Scan Mediebibliotek",
"TaskCleanCacheDescription": "Sletter cache-filer som systemet ikke længere bruger.", "TaskCleanCacheDescription": "Sletter cache-filer som systemet ikke længere bruger.",
"TaskCleanCache": "Ryd Cache-mappe", "TaskCleanCache": "Ryd cache-mappe",
"TasksChannelsCategory": "Internetkanaler", "TasksChannelsCategory": "Internetkanaler",
"TasksApplicationCategory": "Applikation", "TasksApplicationCategory": "Applikation",
"TasksLibraryCategory": "Bibliotek", "TasksLibraryCategory": "Bibliotek",
@ -126,5 +126,9 @@
"External": "Ekstern", "External": "Ekstern",
"HearingImpaired": "Hørehæmmet", "HearingImpaired": "Hørehæmmet",
"TaskRefreshTrickplayImages": "Generér Trickplay Billeder", "TaskRefreshTrickplayImages": "Generér Trickplay Billeder",
"TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker." "TaskRefreshTrickplayImagesDescription": "Laver trickplay forhåndsvisninger for videoer i aktiverede biblioteker.",
"TaskCleanCollectionsAndPlaylists": "Ryd op i samlinger og afspilningslister",
"TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra samlinger og afspilningslister der ikke eksisterer længere.",
"TaskAudioNormalizationDescription": "Skanner filer for data vedrørende audio-normalisering.",
"TaskAudioNormalization": "Audio-normalisering"
} }

View File

@ -126,5 +126,9 @@
"External": "Extern", "External": "Extern",
"HearingImpaired": "Hörgeschädigt", "HearingImpaired": "Hörgeschädigt",
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",
"TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken." "TaskRefreshTrickplayImagesDescription": "Erstellt ein Trickplay-Vorschauen für Videos in aktivierten Bibliotheken.",
"TaskCleanCollectionsAndPlaylists": "Sammlungen und Playlisten aufräumen",
"TaskCleanCollectionsAndPlaylistsDescription": "Lösche nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.",
"TaskAudioNormalization": "Audio Normalisierung",
"TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten."
} }

View File

@ -126,5 +126,9 @@
"External": "Εξωτερικό", "External": "Εξωτερικό",
"HearingImpaired": "Με προβλήματα ακοής", "HearingImpaired": "Με προβλήματα ακοής",
"TaskRefreshTrickplayImages": "Δημιουργήστε εικόνες Trickplay", "TaskRefreshTrickplayImages": "Δημιουργήστε εικόνες Trickplay",
"TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες." "TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες.",
"TaskAudioNormalization": "Ομοιομορφία ήχου",
"TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.",
"TaskCleanCollectionsAndPlaylists": "Καθαρισμός συλλογών και λιστών αναπαραγωγής",
"TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον."
} }

View File

@ -126,5 +126,9 @@
"External": "External", "External": "External",
"HearingImpaired": "Hearing Impaired", "HearingImpaired": "Hearing Impaired",
"TaskRefreshTrickplayImages": "Generate Trickplay Images", "TaskRefreshTrickplayImages": "Generate Trickplay Images",
"TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries." "TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.",
"TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.",
"TaskAudioNormalization": "Audio Normalisation",
"TaskAudioNormalizationDescription": "Scans files for audio normalisation data."
} }

View File

@ -13,7 +13,7 @@
"DeviceOfflineWithName": "{0} has disconnected", "DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected", "DeviceOnlineWithName": "{0} is connected",
"External": "External", "External": "External",
"FailedLoginAttemptWithUserName": "Failed login try from {0}", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites", "Favorites": "Favorites",
"Folders": "Folders", "Folders": "Folders",
"Forced": "Forced", "Forced": "Forced",
@ -106,6 +106,8 @@
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
"TaskRefreshChapterImages": "Extract Chapter Images", "TaskRefreshChapterImages": "Extract Chapter Images",
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
"TaskAudioNormalization": "Audio Normalization",
"TaskAudioNormalizationDescription": "Scans files for audio normalization data.",
"TaskRefreshLibrary": "Scan Media Library", "TaskRefreshLibrary": "Scan Media Library",
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
"TaskCleanLogs": "Clean Log Directory", "TaskCleanLogs": "Clean Log Directory",

View File

@ -11,7 +11,7 @@
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado", "DeviceOnlineWithName": "{0} está conectado",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
@ -124,5 +124,11 @@
"TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.", "TaskKeyframeExtractorDescription": "Extrae los cuadros clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar un buen rato.",
"TaskKeyframeExtractor": "Extractor de Cuadros Clave", "TaskKeyframeExtractor": "Extractor de Cuadros Clave",
"External": "Externo", "External": "Externo",
"HearingImpaired": "Discapacidad Auditiva" "HearingImpaired": "Discapacidad Auditiva",
"TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.",
"TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción",
"TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.",
"TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción."
} }

View File

@ -11,7 +11,7 @@
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado", "DeviceOnlineWithName": "{0} está conectado",
"FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión desde {0}", "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {0}",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
@ -30,7 +30,7 @@
"ItemAddedWithName": "{0} se ha añadido a la biblioteca", "ItemAddedWithName": "{0} se ha añadido a la biblioteca",
"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": "Duración: {0}",
"Latest": "Últimas", "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}",
@ -126,5 +126,9 @@
"External": "Externo", "External": "Externo",
"HearingImpaired": "Discapacidad Auditiva", "HearingImpaired": "Discapacidad Auditiva",
"TaskRefreshTrickplayImages": "Generar miniaturas de línea de tiempo", "TaskRefreshTrickplayImages": "Generar miniaturas de línea de tiempo",
"TaskRefreshTrickplayImagesDescription": "Crear miniaturas de tiempo para videos en las librerías habilitadas." "TaskRefreshTrickplayImagesDescription": "Crear miniaturas de tiempo para videos en las librerías habilitadas.",
"TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.",
"TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización."
} }

View File

@ -112,7 +112,7 @@
"CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}", "CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito", "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
"Application": "Aplicación", "Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanActivityLog": "Limpiar registro de actividades", "TaskCleanActivityLog": "Limpiar registro de actividades",
"Undefined": "Sin definir", "Undefined": "Sin definir",
@ -125,5 +125,9 @@
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave", "TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"HearingImpaired": "Discapacidad auditiva", "HearingImpaired": "Discapacidad auditiva",
"TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.", "TaskRefreshTrickplayImagesDescription": "Crea previsualizaciones para la barra de reproducción en las bibliotecas habilitadas.",
"TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción" "TaskRefreshTrickplayImages": "Generar imágenes de la barra de reproducción",
"TaskAudioNormalization": "Normalización de audio",
"TaskCleanCollectionsAndPlaylistsDescription": "Quita elementos que ya no existen de colecciones y listas de reproducción.",
"TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.",
"TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción"
} }

View File

@ -12,14 +12,118 @@
"Application": "Aplicación", "Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "App: {0}, Dispositivo: {1}",
"HeaderContinueWatching": "Continuar Viendo", "HeaderContinueWatching": "Continuar Viendo",
"HeaderAlbumArtists": "Artistas del Álbum", "HeaderAlbumArtists": "Artistas del álbum",
"Genres": "Géneros", "Genres": "Géneros",
"Folders": "Carpetas", "Folders": "Carpetas",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido de {0}", "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"HeaderFavoriteSongs": "Canciones Favoritas", "HeaderFavoriteSongs": "Canciones Favoritas",
"HeaderFavoriteEpisodes": "Episodios Favoritos", "HeaderFavoriteEpisodes": "Episodios Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos",
"External": "Externo", "External": "Externo",
"Default": "Predeterminado" "Default": "Predeterminado",
"Movies": "Películas",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de la configuración ha sido actualizada",
"MixedContent": "Contenido mixto",
"Music": "Música",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor",
"NotificationOptionVideoPlayback": "Reproducción de video iniciada",
"Sync": "Sincronizar",
"Shows": "Series",
"UserDownloadingItemWithValues": "{0} está descargando {1}",
"UserOfflineFromDevice": "{0} se ha desconectado desde {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}",
"TasksChannelsCategory": "Canales de Internet",
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
"TaskOptimizeDatabaseDescription": "Compacta la base de datos y libera espacio. Ejecutar esta tarea después de escanear la biblioteca o hacer otros cambios que impliquen modificaciones en la base de datos puede mejorar el rendimiento.",
"TaskKeyframeExtractorDescription": "Extrae Fotogramas Clave de los archivos de vídeo para crear Listas de Reproducción HLS más precisas. Esta tarea puede durar mucho tiempo.",
"TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanear archivos para la normalización de data.",
"TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Remover elementos de colecciones y listas de reproducción que no existen.",
"TvShows": "Series de TV",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"TaskRefreshChannels": "Actualizar canales",
"Photos": "Fotos",
"HeaderFavoriteShows": "Programas favoritos",
"TaskCleanActivityLog": "Limpiar registro de actividades",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"System": "Sistema",
"User": "Usuario",
"Forced": "Forzado",
"PluginInstalledWithName": "{0} ha sido instalado",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"TaskUpdatePlugins": "Actualizar Plugins",
"Latest": "Recientes",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"Songs": "Canciones",
"NotificationOptionPluginError": "Falla de plugin",
"ScheduledTaskStartedWithName": "{0} iniciado",
"TasksApplicationCategory": "Aplicación",
"UserDeletedWithName": "El usuario {0} ha sido eliminado",
"TaskRefreshChapterImages": "Extraer imágenes de los capítulos",
"TaskUpdatePluginsDescription": "Descarga e instala actualizaciones para plugins que están configurados para actualizarse automáticamente.",
"TaskRefreshPeopleDescription": "Actualiza metadatos de actores y directores en tu biblioteca de medios.",
"NotificationOptionUserLockedOut": "Usuario bloqueado",
"TaskCleanTranscodeDescription": "Elimina archivos transcodificados que tengan más de un día.",
"TaskCleanTranscode": "Limpiar el directorio de transcodificaciones",
"NotificationOptionPluginUpdateInstalled": "Actualización de plugin instalada",
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"TasksLibraryCategory": "Biblioteca",
"NotificationOptionPluginInstalled": "Plugin instalado",
"UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}",
"VersionNumber": "Versión {0}",
"HeaderNextUp": "A continuación",
"ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}",
"NameSeasonNumber": "Temporada {0}",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"Plugin": "Plugin",
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionTaskFailed": "Falló la tarea programada",
"LabelRunningTimeValue": "Tiempo en ejecución: {0}",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}",
"TaskRefreshLibrary": "Escanear biblioteca de medios",
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
"TasksMaintenanceCategory": "Mantenimiento",
"ProviderValue": "Proveedor: {0}",
"UserCreatedWithName": "El usuario {0} ha sido creado",
"PluginUninstalledWithName": "{0} ha sido desinstalado",
"ValueSpecialEpisodeName": "Especial - {0}",
"ScheduledTaskFailedWithName": "{0} falló",
"TaskCleanLogs": "Limpiar directorio de registros",
"NameInstallFailed": "Falló la instalación de {0}",
"UserLockedOutWithName": "El usuario {0} ha sido bloqueado",
"TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios para encontrar archivos nuevos y actualizar los metadatos.",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo en un momento.",
"Playlists": "Listas de reproducción",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"TaskRefreshPeople": "Actualizar personas",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"HeaderLiveTV": "TV en vivo",
"NameSeasonUnknown": "Temporada desconocida",
"NotificationOptionInstallationFailed": "Fallo de instalación",
"NotificationOptionPluginUninstalled": "Plugin desinstalado",
"TaskCleanCache": "Limpiar directorio caché",
"TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.",
"Inherit": "Heredar",
"HeaderRecordingGroups": "Grupos de grabación",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"TaskOptimizeDatabase": "Optimizar base de datos",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"HearingImpaired": "Discapacidad auditiva",
"HomeVideos": "Videos caseros",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
"MusicVideos": "Videos musicales",
"NewVersionIsAvailable": "Una nueva versión de Jellyfin está disponible para descargar.",
"PluginUpdatedWithName": "{0} ha sido actualizado",
"Undefined": "Sin definir",
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanCacheDescription": "Elimina archivos caché que ya no son necesarios para el sistema.",
"TaskCleanLogsDescription": "Elimina archivos de registro con más de {0} días de antigüedad."
} }

View File

@ -125,5 +125,9 @@
"TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.", "TaskKeyframeExtractorDescription": "Eraldab videofailidest võtmekaadreid, et luua täpsemaid HLS-i esitusloendeid. See ülesanne võib kesta pikka aega.",
"TaskKeyframeExtractor": "Võtmekaadri ekstraktor", "TaskKeyframeExtractor": "Võtmekaadri ekstraktor",
"TaskRefreshTrickplayImages": "Loo eelvaate pildid", "TaskRefreshTrickplayImages": "Loo eelvaate pildid",
"TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud." "TaskRefreshTrickplayImagesDescription": "Loob eelvaated videotele, kus lubatud.",
"TaskAudioNormalization": "Heli Normaliseerimine",
"TaskAudioNormalizationDescription": "Skaneerib faile heli normaliseerimise andmete jaoks.",
"TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest asjad, mida enam ei eksisteeri.",
"TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid"
} }

View File

@ -126,5 +126,7 @@
"External": "خارجی", "External": "خارجی",
"HearingImpaired": "مشکل شنوایی", "HearingImpaired": "مشکل شنوایی",
"TaskRefreshTrickplayImages": "تولید تصاویر Trickplay", "TaskRefreshTrickplayImages": "تولید تصاویر Trickplay",
"TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه." "TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه.",
"TaskCleanCollectionsAndPlaylists": "پاکسازی مجموعه ها و لیست پخش",
"TaskCleanCollectionsAndPlaylistsDescription": "موارد را از مجموعه ها و لیست پخش هایی که دیگر وجود ندارند حذف میکند."
} }

View File

@ -125,5 +125,9 @@
"External": "Ulkoinen", "External": "Ulkoinen",
"HearingImpaired": "Kuulorajoitteinen", "HearingImpaired": "Kuulorajoitteinen",
"TaskRefreshTrickplayImages": "Luo Trickplay-kuvat", "TaskRefreshTrickplayImages": "Luo Trickplay-kuvat",
"TaskRefreshTrickplayImagesDescription": "Luo Trickplay-esikatselut käytössä olevien kirjastojen videoista." "TaskRefreshTrickplayImagesDescription": "Luo Trickplay-esikatselut käytössä olevien kirjastojen videoista.",
"TaskCleanCollectionsAndPlaylistsDescription": "Poistaa kohteet kokoelmista ja soittolistoista joita ei ole enää olemassa.",
"TaskCleanCollectionsAndPlaylists": "Puhdista kokoelmat ja soittolistat",
"TaskAudioNormalization": "Äänenvoimakkuuden normalisointi",
"TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja."
} }

View File

@ -11,7 +11,7 @@
"Collections": "Collections", "Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté", "DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté", "DeviceOnlineWithName": "{0} est connecté",
"FailedLoginAttemptWithUserName": "Tentative de connexion échoué par {0}", "FailedLoginAttemptWithUserName": "Tentative de connexion échouée par {0}",
"Favorites": "Favoris", "Favorites": "Favoris",
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
@ -39,7 +39,7 @@
"MixedContent": "Contenu mixte", "MixedContent": "Contenu mixte",
"Movies": "Films", "Movies": "Films",
"Music": "Musique", "Music": "Musique",
"MusicVideos": "Vidéos musicales", "MusicVideos": "Vidéoclips",
"NameInstallFailed": "échec d'installation de {0}", "NameInstallFailed": "échec d'installation de {0}",
"NameSeasonNumber": "Saison {0}", "NameSeasonNumber": "Saison {0}",
"NameSeasonUnknown": "Saison Inconnue", "NameSeasonUnknown": "Saison Inconnue",
@ -126,5 +126,9 @@
"External": "Externe", "External": "Externe",
"HearingImpaired": "Malentendants", "HearingImpaired": "Malentendants",
"TaskRefreshTrickplayImages": "Générer des images Trickplay", "TaskRefreshTrickplayImages": "Générer des images Trickplay",
"TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées." "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.",
"TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture",
"TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus.",
"TaskAudioNormalization": "Normalisation audio",
"TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio."
} }

View File

@ -126,5 +126,9 @@
"External": "Externe", "External": "Externe",
"HearingImpaired": "Malentendants", "HearingImpaired": "Malentendants",
"TaskRefreshTrickplayImages": "Générer des images Trickplay", "TaskRefreshTrickplayImages": "Générer des images Trickplay",
"TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées." "TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées.",
"TaskCleanCollectionsAndPlaylists": "Nettoyer les collections et les listes de lecture",
"TaskCleanCollectionsAndPlaylistsDescription": "Supprime les éléments des collections et des listes de lecture qui n'existent plus.",
"TaskAudioNormalization": "Normalisation audio",
"TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio."
} }

View File

@ -1,3 +1,16 @@
{ {
"Albums": "Albaim" "Albums": "Albaim",
"Artists": "Ealaíontóir",
"AuthenticationSucceededWithUserName": "{0} fíordheimhnithe",
"Books": "leabhair",
"CameraImageUploadedFrom": "Tá íomhá ceamara nua uaslódáilte ó {0}",
"Channels": "Cainéil",
"ChapterNameValue": "Caibidil {0}",
"Collections": "Bailiúcháin",
"Default": "Mainneachtain",
"DeviceOfflineWithName": "scoireadh {0}",
"DeviceOnlineWithName": "{0} ceangailte",
"External": "Forimeallach",
"FailedLoginAttemptWithUserName": "Iarracht ar theip ar fhíordheimhniú ó {0}",
"Favorites": "Ceanáin"
} }

View File

@ -126,5 +126,9 @@
"External": "חיצוני", "External": "חיצוני",
"HearingImpaired": "לקוי שמיעה", "HearingImpaired": "לקוי שמיעה",
"TaskRefreshTrickplayImages": "יצירת תמונות המחשה", "TaskRefreshTrickplayImages": "יצירת תמונות המחשה",
"TaskRefreshTrickplayImagesDescription": "יוצר תמונות המחשה לסרטונים שפעילים בספריות." "TaskRefreshTrickplayImagesDescription": "יוצר תמונות המחשה לסרטונים שפעילים בספריות.",
"TaskAudioNormalization": "נרמול שמע",
"TaskCleanCollectionsAndPlaylistsDescription": "מנקה פריטים לא קיימים מאוספים ורשימות השמעה.",
"TaskAudioNormalizationDescription": "מחפש קבצי נורמליזציה של שמע.",
"TaskCleanCollectionsAndPlaylists": "מנקה אוספים ורשימות השמעה"
} }

View File

@ -14,7 +14,7 @@
"Forced": "बलपूर्वक", "Forced": "बलपूर्वक",
"Folders": "फ़ोल्डर", "Folders": "फ़ोल्डर",
"Favorites": "पसंदीदा", "Favorites": "पसंदीदा",
"FailedLoginAttemptWithUserName": "{0} से लॉगिन असफल हुआ", "FailedLoginAttemptWithUserName": "{0} से संप्रवेश असफल हुआ",
"DeviceOnlineWithName": "{0} कनेक्ट हो गया है", "DeviceOnlineWithName": "{0} कनेक्ट हो गया है",
"DeviceOfflineWithName": "{0} डिस्कनेक्ट हो गया है", "DeviceOfflineWithName": "{0} डिस्कनेक्ट हो गया है",
"Default": "प्राथमिक", "Default": "प्राथमिक",
@ -125,5 +125,7 @@
"TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कॉन्फ़िगरेशन के आधार पर लापता उपशीर्षक के लिए इंटरनेट खोजता है।", "TaskDownloadMissingSubtitlesDescription": "मेटाडेटा कॉन्फ़िगरेशन के आधार पर लापता उपशीर्षक के लिए इंटरनेट खोजता है।",
"TaskKeyframeExtractorDescription": "अधिक सटीक एचएलएस प्लेलिस्ट बनाने के लिए वीडियो फ़ाइलों से मुख्य-फ़्रेम निकालता है। यह कार्य लंबे समय तक चल सकता है।", "TaskKeyframeExtractorDescription": "अधिक सटीक एचएलएस प्लेलिस्ट बनाने के लिए वीडियो फ़ाइलों से मुख्य-फ़्रेम निकालता है। यह कार्य लंबे समय तक चल सकता है।",
"TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे", "TaskRefreshTrickplayImages": "ट्रिकप्लै चित्रों को सृजन करे",
"TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे." "TaskRefreshTrickplayImagesDescription": "नियत संग्रहों में चलचित्रों का ट्रीकप्लै दर्शनों को सृजन करे.",
"TaskAudioNormalization": "श्रव्य सामान्यीकरण",
"TaskAudioNormalizationDescription": "श्रव्य सामान्यीकरण के लिए फाइलें अन्वेषण करें"
} }

View File

@ -126,5 +126,6 @@
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.", "TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.",
"HearingImpaired": "Oštećen sluh", "HearingImpaired": "Oštećen sluh",
"TaskRefreshTrickplayImages": "Generiraj Trickplay Slike", "TaskRefreshTrickplayImages": "Generiraj Trickplay Slike",
"TaskRefreshTrickplayImagesDescription": "Kreira trickplay pretpreglede za videe u omogućenim knjižnicama." "TaskRefreshTrickplayImagesDescription": "Kreira trickplay pretpreglede za videe u omogućenim knjižnicama.",
"TaskAudioNormalization": "Normalizacija zvuka"
} }

View File

@ -1,13 +1,13 @@
{ {
"Albums": "Albumok", "Albums": "Albumok",
"AppDeviceValues": "Program: {0}, eszköz: {1}", "AppDeviceValues": "Program: {0}, Eszköz: {1}",
"Application": "Alkalmazás", "Application": "Alkalmazás",
"Artists": "Előadók", "Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve", "AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve",
"Books": "Könyvek", "Books": "Könyvek",
"CameraImageUploadedFrom": "Új kamerakép feltöltve innen: {0}", "CameraImageUploadedFrom": "Új kamerakép lett feltöltve innen: {0}",
"Channels": "Csatornák", "Channels": "Csatornák",
"ChapterNameValue": "{0}. jelenet", "ChapterNameValue": "Jelenet {0}",
"Collections": "Gyűjtemények", "Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett", "DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett", "DeviceOnlineWithName": "{0} belépett",
@ -15,27 +15,27 @@
"Favorites": "Kedvencek", "Favorites": "Kedvencek",
"Folders": "Könyvtárak", "Folders": "Könyvtárak",
"Genres": "Műfajok", "Genres": "Műfajok",
"HeaderAlbumArtists": "Albumelőadók", "HeaderAlbumArtists": "Album előadók",
"HeaderContinueWatching": "Megtekintés folytatása", "HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteAlbums": "Kedvenc Albumok",
"HeaderFavoriteArtists": "Kedvenc előadók", "HeaderFavoriteArtists": "Kedvenc Előadók",
"HeaderFavoriteEpisodes": "Kedvenc epizódok", "HeaderFavoriteEpisodes": "Kedvenc Epizódok",
"HeaderFavoriteShows": "Kedvenc sorozatok", "HeaderFavoriteShows": "Kedvenc Sorozatok",
"HeaderFavoriteSongs": "Kedvenc számok", "HeaderFavoriteSongs": "Kedvenc Dalok",
"HeaderLiveTV": "Élő TV", "HeaderLiveTV": "Élő TV",
"HeaderNextUp": "Következik", "HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Felvételi csoportok", "HeaderRecordingGroups": "Felvevő Csoportok",
"HomeVideos": "Házi videók", "HomeVideos": "Otthoni Videók",
"Inherit": "Örökölt", "Inherit": "Örökölt",
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz", "ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból", "ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
"LabelIpAddressValue": "IP-cím: {0}", "LabelIpAddressValue": "IP-cím: {0}",
"LabelRunningTimeValue": "Lejátszási idő: {0}", "LabelRunningTimeValue": "Lejátszási idő: {0}",
"Latest": "Legújabb", "Latest": "Legújabb",
"MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve", "MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve lett",
"MessageApplicationUpdatedTo": "A Jellyfin kiszolgáló frissítve lett a következőre: {0}", "MessageApplicationUpdatedTo": "A Jellyfin kiszolgáló frissítve lett a következőre: {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve: {0}", "MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve lett: {0}",
"MessageServerConfigurationUpdated": "Kiszolgálókonfiguráció frissítve", "MessageServerConfigurationUpdated": "Kiszolgálókonfiguráció frissítve lett",
"MixedContent": "Vegyes tartalom", "MixedContent": "Vegyes tartalom",
"Movies": "Filmek", "Movies": "Filmek",
"Music": "Zenék", "Music": "Zenék",
@ -46,7 +46,7 @@
"NewVersionIsAvailable": "Letölthető a Jellyfin kiszolgáló új verziója.", "NewVersionIsAvailable": "Letölthető a Jellyfin kiszolgáló új verziója.",
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz", "NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
"NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve", "NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
"NotificationOptionAudioPlayback": "Hanglejátszás elkezdve", "NotificationOptionAudioPlayback": "Hanglejátszás elkezdődött",
"NotificationOptionAudioPlaybackStopped": "Hanglejátszás leállítva", "NotificationOptionAudioPlaybackStopped": "Hanglejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamerakép feltöltve", "NotificationOptionCameraImageUploaded": "Kamerakép feltöltve",
"NotificationOptionInstallationFailed": "Telepítési hiba", "NotificationOptionInstallationFailed": "Telepítési hiba",
@ -126,5 +126,9 @@
"External": "Külső", "External": "Külső",
"HearingImpaired": "Hallássérült", "HearingImpaired": "Hallássérült",
"TaskRefreshTrickplayImages": "Trickplay képek generálása", "TaskRefreshTrickplayImages": "Trickplay képek generálása",
"TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz." "TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz.",
"TaskAudioNormalization": "Hangerő Normalizáció",
"TaskCleanCollectionsAndPlaylistsDescription": "Nem létező elemek törlése a gyűjteményekből és lejátszási listákról.",
"TaskAudioNormalizationDescription": "Hangerő normalizációs adatok keresése.",
"TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása"
} }

View File

@ -81,7 +81,7 @@
"Movies": "Film", "Movies": "Film",
"MessageServerConfigurationUpdated": "Konfigurasi server telah diperbarui", "MessageServerConfigurationUpdated": "Konfigurasi server telah diperbarui",
"MessageNamedServerConfigurationUpdatedWithValue": "Bagian konfigurasi server {0} telah diperbarui", "MessageNamedServerConfigurationUpdatedWithValue": "Bagian konfigurasi server {0} telah diperbarui",
"FailedLoginAttemptWithUserName": "Gagal melakukan login dari {0}", "FailedLoginAttemptWithUserName": "Gagal upaya login dari {0}",
"CameraImageUploadedFrom": "Sebuah gambar kamera baru telah diunggah dari {0}", "CameraImageUploadedFrom": "Sebuah gambar kamera baru telah diunggah dari {0}",
"DeviceOfflineWithName": "{0} telah terputus", "DeviceOfflineWithName": "{0} telah terputus",
"DeviceOnlineWithName": "{0} telah terhubung", "DeviceOnlineWithName": "{0} telah terhubung",
@ -125,5 +125,9 @@
"External": "Luar", "External": "Luar",
"HearingImpaired": "Gangguan Pendengaran", "HearingImpaired": "Gangguan Pendengaran",
"TaskRefreshTrickplayImages": "Hasilkan Gambar Trickplay", "TaskRefreshTrickplayImages": "Hasilkan Gambar Trickplay",
"TaskRefreshTrickplayImagesDescription": "Buat pratinjau trickplay untuk video di perpustakaan yang diaktifkan." "TaskRefreshTrickplayImagesDescription": "Buat pratinjau trickplay untuk video di perpustakaan yang diaktifkan.",
"TaskAudioNormalizationDescription": "Pindai file untuk data normalisasi audio.",
"TaskAudioNormalization": "Normalisasi Audio",
"TaskCleanCollectionsAndPlaylists": "Bersihkan koleksi dan daftar putar",
"TaskCleanCollectionsAndPlaylistsDescription": "Menghapus item dari koleksi dan daftar putar yang sudah tidak ada."
} }

View File

@ -17,7 +17,7 @@
"Genres": "Stefnur", "Genres": "Stefnur",
"Folders": "Möppur", "Folders": "Möppur",
"Favorites": "Uppáhalds", "Favorites": "Uppáhalds",
"FailedLoginAttemptWithUserName": "{0} reyndi að auðkenna sig", "FailedLoginAttemptWithUserName": "{0} mistókst að auðkenna sig",
"DeviceOnlineWithName": "{0} hefur tengst", "DeviceOnlineWithName": "{0} hefur tengst",
"DeviceOfflineWithName": "{0} hefur aftengst", "DeviceOfflineWithName": "{0} hefur aftengst",
"Collections": "Söfn", "Collections": "Söfn",
@ -123,5 +123,11 @@
"TaskRefreshChapterImages": "Plokka kafla-myndir", "TaskRefreshChapterImages": "Plokka kafla-myndir",
"TaskCleanActivityLogDescription": "Eyðir virkniskráningarfærslum sem hafa náð settum hámarksaldri.", "TaskCleanActivityLogDescription": "Eyðir virkniskráningarfærslum sem hafa náð settum hámarksaldri.",
"Forced": "Þvingað", "Forced": "Þvingað",
"External": "Útvær" "External": "Útvær",
"TaskRefreshTrickplayImagesDescription": "Býr til hraðspilunarmyndir fyrir myndbönd í virkum söfnum.",
"TaskRefreshTrickplayImages": "Búa til hraðspilunarmyndir",
"TaskAudioNormalization": "Hljóðstöðlun",
"TaskAudioNormalizationDescription": "Leitar að hljóðstöðlunargögnum í skrám.",
"TaskCleanCollectionsAndPlaylists": "Hreinsa söfn og spilunarlista",
"TaskCleanCollectionsAndPlaylistsDescription": "Fjarlægir hluti úr söfnum og spilalistum sem eru ekki lengur til."
} }

View File

@ -51,10 +51,10 @@
"NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata", "NotificationOptionCameraImageUploaded": "Immagine fotocamera caricata",
"NotificationOptionInstallationFailed": "Installazione fallita", "NotificationOptionInstallationFailed": "Installazione fallita",
"NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto", "NotificationOptionNewLibraryContent": "Nuovo contenuto aggiunto",
"NotificationOptionPluginError": "Errore del Plug-in", "NotificationOptionPluginError": "Errore del plugin",
"NotificationOptionPluginInstalled": "Plug-in installato", "NotificationOptionPluginInstalled": "Plugin installato",
"NotificationOptionPluginUninstalled": "Plug-in disinstallato", "NotificationOptionPluginUninstalled": "Plugin disinstallato",
"NotificationOptionPluginUpdateInstalled": "Aggiornamento del plug-in installato", "NotificationOptionPluginUpdateInstalled": "Aggiornamento plugin installato",
"NotificationOptionServerRestartRequired": "Riavvio del server necessario", "NotificationOptionServerRestartRequired": "Riavvio del server necessario",
"NotificationOptionTaskFailed": "Operazione pianificata fallita", "NotificationOptionTaskFailed": "Operazione pianificata fallita",
"NotificationOptionUserLockedOut": "Utente bloccato", "NotificationOptionUserLockedOut": "Utente bloccato",
@ -68,10 +68,10 @@
"PluginUpdatedWithName": "{0} è stato aggiornato", "PluginUpdatedWithName": "{0} è stato aggiornato",
"ProviderValue": "Provider: {0}", "ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} fallito", "ScheduledTaskFailedWithName": "{0} fallito",
"ScheduledTaskStartedWithName": "{0} avviati", "ScheduledTaskStartedWithName": "{0} avviato",
"ServerNameNeedsToBeRestarted": "{0} deve essere riavviato", "ServerNameNeedsToBeRestarted": "{0} deve essere riavviato",
"Shows": "Serie TV", "Shows": "Serie TV",
"Songs": "Canzoni", "Songs": "Brani",
"StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.", "StartupEmbyServerIsLoading": "Jellyfin server si sta avviando. Per favore riprova più tardi.",
"SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}", "SubtitleDownloadFailureForItem": "Impossibile scaricare i sottotitoli per {0}",
"SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}", "SubtitleDownloadFailureFromForItem": "Impossibile scaricare i sottotitoli da {0} per {1}",
@ -83,48 +83,52 @@
"UserDeletedWithName": "L'utente {0} è stato rimosso", "UserDeletedWithName": "L'utente {0} è stato rimosso",
"UserDownloadingItemWithValues": "{0} sta scaricando {1}", "UserDownloadingItemWithValues": "{0} sta scaricando {1}",
"UserLockedOutWithName": "L'utente {0} è stato bloccato", "UserLockedOutWithName": "L'utente {0} è stato bloccato",
"UserOfflineFromDevice": "{0} si è disconnesso su {1}", "UserOfflineFromDevice": "{0} si è disconnesso da {1}",
"UserOnlineFromDevice": "{0} è online su {1}", "UserOnlineFromDevice": "{0} è online su {1}",
"UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}", "UserPasswordChangedWithName": "La password è stata cambiata per l'utente {0}",
"UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}", "UserPolicyUpdatedWithName": "La policy dell'utente è stata aggiornata per {0}",
"UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di \"{1}\" su {2}", "UserStartedPlayingItemWithValues": "{0} ha avviato la riproduzione di {1} su {2}",
"UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}", "UserStoppedPlayingItemWithValues": "{0} ha interrotto la riproduzione di {1} su {2}",
"ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale", "ValueHasBeenAddedToLibrary": "{0} è stato aggiunto alla tua libreria multimediale",
"ValueSpecialEpisodeName": "Speciale - {0}", "ValueSpecialEpisodeName": "Speciale - {0}",
"VersionNumber": "Versione {0}", "VersionNumber": "Versione {0}",
"TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali Internet.", "TaskRefreshChannelsDescription": "Aggiorna le informazioni dei canali internet.",
"TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.", "TaskDownloadMissingSubtitlesDescription": "Cerca su internet i sottotitoli mancanti basandosi sulle configurazioni dei metadati.",
"TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti", "TaskDownloadMissingSubtitles": "Scarica i sottotitoli mancanti",
"TaskRefreshChannels": "Aggiorna i canali", "TaskRefreshChannels": "Aggiorna canali",
"TaskCleanTranscodeDescription": "Cancella i file di transcode più vecchi di un giorno.", "TaskCleanTranscodeDescription": "Cancella i file di transcodifica più vecchi di un giorno.",
"TaskCleanTranscode": "Svuota la cartella del transcoding", "TaskCleanTranscode": "Svuota la cartella della transcodifica",
"TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin che sono stati configurati per essere aggiornati contemporaneamente.", "TaskUpdatePluginsDescription": "Scarica e installa gli aggiornamenti per i plugin configurati per l'aggiornamento automatico.",
"TaskUpdatePlugins": "Aggiorna i Plugin", "TaskUpdatePlugins": "Aggiorna i plugin",
"TaskRefreshPeopleDescription": "Aggiorna i metadati per gli attori e registi nella tua libreria multimediale.", "TaskRefreshPeopleDescription": "Aggiorna i metadati degli attori e registi nella tua libreria.",
"TaskRefreshPeople": "Aggiornamento Persone", "TaskRefreshPeople": "Aggiorna Persone",
"TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.", "TaskCleanLogsDescription": "Rimuovi i file di log più vecchi di {0} giorni.",
"TaskCleanLogs": "Pulisci la cartella dei log", "TaskCleanLogs": "Pulisci la cartella dei log",
"TaskRefreshLibraryDescription": "Analizza la tua libreria multimediale per nuovi file e rinnova i metadati.", "TaskRefreshLibraryDescription": "Scansiona la libreria alla ricerca di nuovi file e aggiorna i metadati.",
"TaskRefreshLibrary": "Scan Librerie", "TaskRefreshLibrary": "Scansione della libreria",
"TaskRefreshChapterImagesDescription": "Crea le thumbnail per i video che hanno capitoli.", "TaskRefreshChapterImagesDescription": "Crea le miniature per i video che hanno capitoli.",
"TaskRefreshChapterImages": "Estrai immagini capitolo", "TaskRefreshChapterImages": "Estrai immagini capitolo",
"TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.", "TaskCleanCacheDescription": "Cancella i file di cache non più necessari al sistema.",
"TaskCleanCache": "Pulisci la directory della cache", "TaskCleanCache": "Pulisci la cartella della cache",
"TasksChannelsCategory": "Canali su Internet", "TasksChannelsCategory": "Canali su Internet",
"TasksApplicationCategory": "Applicazione", "TasksApplicationCategory": "Applicazione",
"TasksLibraryCategory": "Libreria", "TasksLibraryCategory": "Libreria",
"TasksMaintenanceCategory": "Manutenzione", "TasksMaintenanceCategory": "Manutenzione",
"TaskCleanActivityLog": "Attività di Registro Completate", "TaskCleanActivityLog": "Attività di Registro Completate",
"TaskCleanActivityLogDescription": "Elimina gli inserimenti nel registro delle attività più vecchie delletà configurata.", "TaskCleanActivityLogDescription": "Elimina le voci del registro delle attività più vecchie delletà configurata.",
"Undefined": "Non Definito", "Undefined": "Non Definito",
"Forced": "Forzato", "Forced": "Forzato",
"Default": "Predefinito", "Default": "Predefinito",
"TaskOptimizeDatabaseDescription": "Compatta Database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altri cambiamenti inerenti il database potrebbe aumentarne la performance.", "TaskOptimizeDatabaseDescription": "Compatta database e tronca spazi liberi. Eseguire questa azione dopo la scansione o dopo aver fatto altre modifiche inerenti il database potrebbe aumentarne le prestazioni.",
"TaskOptimizeDatabase": "Ottimizza Database", "TaskOptimizeDatabase": "Ottimizza database",
"TaskKeyframeExtractor": "Estrattore di Keyframe", "TaskKeyframeExtractor": "Estrattore di Keyframe",
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.", "TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
"External": "Esterno", "External": "Esterno",
"HearingImpaired": "con problemi di udito", "HearingImpaired": "Non Udenti",
"TaskRefreshTrickplayImages": "Genera immagini Trickplay", "TaskRefreshTrickplayImages": "Genera immagini Trickplay",
"TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate." "TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate.",
"TaskCleanCollectionsAndPlaylists": "Ripulire le collezioni e le playlist",
"TaskCleanCollectionsAndPlaylistsDescription": "Rimuove gli elementi dalle collezioni e dalle playlist che non esistono più.",
"TaskAudioNormalization": "Normalizzazione dell'audio",
"TaskAudioNormalizationDescription": "Scansiona i file alla ricerca dei dati per la normalizzazione dell'audio."
} }

View File

@ -125,5 +125,9 @@
"External": "外部", "External": "外部",
"HearingImpaired": "聴覚障害の方", "HearingImpaired": "聴覚障害の方",
"TaskRefreshTrickplayImages": "トリックプレー画像を生成", "TaskRefreshTrickplayImages": "トリックプレー画像を生成",
"TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。" "TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。",
"TaskCleanCollectionsAndPlaylists": "コレクションとプレイリストをクリーンアップ",
"TaskAudioNormalization": "音声の正規化",
"TaskAudioNormalizationDescription": "音声の正規化データのためにファイルをスキャンします。",
"TaskCleanCollectionsAndPlaylistsDescription": "在しなくなったコレクションやプレイリストからアイテムを削除します。"
} }

View File

@ -124,5 +124,6 @@
"TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.", "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.",
"TaskKeyframeExtractor": "키프레임 추출", "TaskKeyframeExtractor": "키프레임 추출",
"External": "외부", "External": "외부",
"HearingImpaired": "청각 장애" "HearingImpaired": "청각 장애",
"TaskCleanCollectionsAndPlaylists": "컬렉션과 재생목록 정리"
} }

View File

@ -126,5 +126,7 @@
"External": "Išorinis", "External": "Išorinis",
"HearingImpaired": "Su klausos sutrikimais", "HearingImpaired": "Su klausos sutrikimais",
"TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus", "TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus",
"TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose." "TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose.",
"TaskCleanCollectionsAndPlaylists": "Sutvarko duomenis jūsų kolekcijose ir grojaraščiuose.",
"TaskCleanCollectionsAndPlaylistsDescription": "Pašalina nebeegzistuojančius elementus iš kolekcijų ir grojaraščių."
} }

View File

@ -17,7 +17,7 @@
"Inherit": "Pārmantot", "Inherit": "Pārmantot",
"AppDeviceValues": "Lietotne: {0}, Ierīce: {1}", "AppDeviceValues": "Lietotne: {0}, Ierīce: {1}",
"VersionNumber": "Versija {0}", "VersionNumber": "Versija {0}",
"ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots jūsu multvides bibliotēkai", "ValueHasBeenAddedToLibrary": "{0} tika pievienots jūsu multvides bibliotēkai",
"UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}", "UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
"UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}", "UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
"UserPasswordChangedWithName": "Lietotāja {0} parole tika nomainīta", "UserPasswordChangedWithName": "Lietotāja {0} parole tika nomainīta",
@ -76,7 +76,7 @@
"Genres": "Žanri", "Genres": "Žanri",
"Folders": "Mapes", "Folders": "Mapes",
"Favorites": "Izlase", "Favorites": "Izlase",
"FailedLoginAttemptWithUserName": "Neizdevies ieiešanas mēģinājums no {0}", "FailedLoginAttemptWithUserName": "Neveiksmīgs ielogošanos mēģinājums no {0}",
"DeviceOnlineWithName": "Savienojums ar {0} ir izveidots", "DeviceOnlineWithName": "Savienojums ar {0} ir izveidots",
"DeviceOfflineWithName": "Savienojums ar {0} ir pārtraukts", "DeviceOfflineWithName": "Savienojums ar {0} ir pārtraukts",
"Collections": "Kolekcijas", "Collections": "Kolekcijas",
@ -95,7 +95,7 @@
"TaskRefreshChapterImages": "Izvilkt nodaļu attēlus", "TaskRefreshChapterImages": "Izvilkt nodaļu attēlus",
"TasksApplicationCategory": "Lietotne", "TasksApplicationCategory": "Lietotne",
"TasksLibraryCategory": "Bibliotēka", "TasksLibraryCategory": "Bibliotēka",
"TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus balstoties uz metadatu uzstādījumiem.", "TaskDownloadMissingSubtitlesDescription": "Meklē trūkstošus subtitrus internēta balstoties uz metadatu uzstādījumiem.",
"TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošos subtitrus", "TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošos subtitrus",
"TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.", "TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.",
"TaskRefreshChannels": "Atjaunot kanālus", "TaskRefreshChannels": "Atjaunot kanālus",
@ -105,8 +105,8 @@
"TaskUpdatePlugins": "Atjaunot paplašinājumus", "TaskUpdatePlugins": "Atjaunot paplašinājumus",
"TaskRefreshPeopleDescription": "Atjauno metadatus aktieriem un direktoriem jūsu multivides bibliotēkā.", "TaskRefreshPeopleDescription": "Atjauno metadatus aktieriem un direktoriem jūsu multivides bibliotēkā.",
"TaskRefreshPeople": "Atjaunot cilvēkus", "TaskRefreshPeople": "Atjaunot cilvēkus",
"TaskCleanLogsDescription": "Nodzēš logdatnes, kas ir senākas par {0} dienām.", "TaskCleanLogsDescription": "Nodzēš žurnāla ierakstus, kas ir senāki par {0} dienām.",
"TaskCleanLogs": "Iztīrīt logdatņu mapi", "TaskCleanLogs": "Iztīrīt žurnālu mapi",
"TaskRefreshLibraryDescription": "Skenē jūsu multivides bibliotēku, lai atrastu jaunas datnes, un atsvaidzina metadatus.", "TaskRefreshLibraryDescription": "Skenē jūsu multivides bibliotēku, lai atrastu jaunas datnes, un atsvaidzina metadatus.",
"TaskRefreshLibrary": "Skenēt multivides bibliotēku", "TaskRefreshLibrary": "Skenēt multivides bibliotēku",
"TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.", "TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.",
@ -125,5 +125,9 @@
"TaskKeyframeExtractor": "Atslēgkadru ekstraktors", "TaskKeyframeExtractor": "Atslēgkadru ekstraktors",
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs.", "TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs.",
"TaskRefreshTrickplayImages": "Ģenerēt partīšanas attēlus", "TaskRefreshTrickplayImages": "Ģenerēt partīšanas attēlus",
"TaskRefreshTrickplayImagesDescription": "Izveido priekšskatījumus videoklipu pārtīšanai iespējotajās bibliotēkās." "TaskRefreshTrickplayImagesDescription": "Izveido priekšskatījumus videoklipu pārtīšanai iespējotajās bibliotēkās.",
"TaskAudioNormalization": "Audio normalizācija",
"TaskCleanCollectionsAndPlaylistsDescription": "Noņem elemēntus no kolekcijām un atskaņošanas sarakstiem, kuri vairs neeksistē.",
"TaskAudioNormalizationDescription": "Skanē failus priekš audio normālizācijas informācijas.",
"TaskCleanCollectionsAndPlaylists": "Notīrīt kolekcijas un atskaņošanas sarakstus"
} }

View File

@ -6,7 +6,7 @@
"ChapterNameValue": "അധ്യായം {0}", "ChapterNameValue": "അധ്യായം {0}",
"DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു", "DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു",
"DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു", "DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു",
"FailedLoginAttemptWithUserName": "{0} - എന്നതിൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു", "FailedLoginAttemptWithUserName": "{0}ൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
"Forced": "നിർബന്ധിച്ചു", "Forced": "നിർബന്ധിച്ചു",
"HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ", "HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ",
"HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ", "HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ",
@ -123,5 +123,11 @@
"HearingImpaired": "കേൾവി തകരാറുകൾ", "HearingImpaired": "കേൾവി തകരാറുകൾ",
"External": "പുറമേയുള്ള", "External": "പുറമേയുള്ള",
"TaskKeyframeExtractorDescription": "കൂടുതൽ കൃത്യമായ HLS പ്ലേലിസ്റ്റുകൾ സൃഷ്‌ടിക്കുന്നതിന് വീഡിയോ ഫയലുകളിൽ നിന്ന് കീഫ്രെയിമുകൾ എക്‌സ്‌ട്രാക്‌റ്റ് ചെയ്യുന്നു. ഈ പ്രവർത്തനം പൂർത്തിയാവാൻ കുറച്ചധികം സമയം എടുത്തേക്കാം.", "TaskKeyframeExtractorDescription": "കൂടുതൽ കൃത്യമായ HLS പ്ലേലിസ്റ്റുകൾ സൃഷ്‌ടിക്കുന്നതിന് വീഡിയോ ഫയലുകളിൽ നിന്ന് കീഫ്രെയിമുകൾ എക്‌സ്‌ട്രാക്‌റ്റ് ചെയ്യുന്നു. ഈ പ്രവർത്തനം പൂർത്തിയാവാൻ കുറച്ചധികം സമയം എടുത്തേക്കാം.",
"TaskKeyframeExtractor": "കീഫ്രെയിം എക്സ്ട്രാക്റ്റർ" "TaskKeyframeExtractor": "കീഫ്രെയിം എക്സ്ട്രാക്റ്റർ",
"TaskCleanCollectionsAndPlaylistsDescription": "നിലവിലില്ലാത്ത ശേഖരങ്ങളിൽ നിന്നും പ്ലേലിസ്റ്റുകളിൽ നിന്നും ഇനങ്ങൾ നീക്കംചെയ്യുന്നു.",
"TaskCleanCollectionsAndPlaylists": "ശേഖരങ്ങളും പ്ലേലിസ്റ്റുകളും വൃത്തിയാക്കുക",
"TaskAudioNormalization": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുക",
"TaskAudioNormalizationDescription": "സാധാരണ ശബ്ദ നിലയിലെത്തിലെത്തിക്കുന്ന ഡാറ്റയ്ക്കായി ഫയലുകൾ സ്കാൻ ചെയ്യുക.",
"TaskRefreshTrickplayImages": "ട്രിക്ക് പ്ലേ ചിത്രങ്ങൾ സൃഷ്ടിക്കുക",
"TaskRefreshTrickplayImagesDescription": "പ്രവർത്തനക്ഷമമാക്കിയ ലൈബ്രറികളിൽ വീഡിയോകൾക്കായി ട്രിക്ക്പ്ലേ പ്രിവ്യൂകൾ സൃഷ്ടിക്കുന്നു."
} }

View File

@ -0,0 +1,133 @@
{
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Apparat: {1}",
"Application": "Applikazzjoni",
"Artists": "Artisti",
"AuthenticationSucceededWithUserName": "{1} awtentikat b'suċċess",
"Books": "Kotba",
"CameraImageUploadedFrom": "Ttellgħet immaġni ġdida tal-kamera minn {1}",
"Channels": "Kanali",
"ChapterNameValue": "Kapitlu {0}",
"Collections": "Kollezzjonijiet",
"DeviceOfflineWithName": "{0} inqatgħa",
"DeviceOnlineWithName": "{0} qabad",
"External": "Estern",
"FailedLoginAttemptWithUserName": "Tentattiv t'aċċess fallut minn {0}",
"Favorites": "Favoriti",
"Forced": "Sfurzat",
"Genres": "Ġeneri",
"HeaderAlbumArtists": "Artisti tal-album",
"HeaderContinueWatching": "Kompli Segwi",
"HeaderFavoriteAlbums": "Albums Favoriti",
"HeaderFavoriteArtists": "Artisti Favoriti",
"HeaderFavoriteEpisodes": "Episodji Favoriti",
"HeaderFavoriteShows": "Programmi Favoriti",
"HeaderFavoriteSongs": "Kanzunetti Favoriti",
"HeaderNextUp": "Li Jmiss",
"SubtitleDownloadFailureFromForItem": "Is-sottotitli naqsu milli jitniżżlu minn {0} għal {1}",
"UserPasswordChangedWithName": "Il-password inbidel għall-utent {0}",
"TaskUpdatePluginsDescription": "Iniżżel u jinstalla aġġornamenti għal plugins li huma kkonfigurati biex jaġġornaw awtomatikament.",
"TaskDownloadMissingSubtitlesDescription": "Ifittex fuq l-internet għal sottotitli neqsin abbażi tal-konfigurazzjoni tal-metadata.",
"TaskOptimizeDatabaseDescription": "Jikkompatti d-database u jaqta' l-ispazju ħieles. It-tħaddim ta' dan il-kompitu wara li tiskennja l-librerija jew tagħmel bidliet oħra li jimplikaw modifiki fid-database jistgħu jtejbu l-prestazzjoni.",
"Default": "Standard",
"Folders": "Folders",
"HeaderLiveTV": "TV Dirett",
"HeaderRecordingGroups": "Gruppi ta' Reġistrazzjoni",
"HearingImpaired": "Nuqqas ta' Smigħ",
"HomeVideos": "Vidjows Personali",
"Inherit": "Jiret",
"ItemAddedWithName": "{0} ġie miżjud mal-librerija",
"ItemRemovedWithName": "{0} tneħħa mil-librerija",
"LabelIpAddressValue": "Indirizz IP: {0}",
"Latest": "Tal-Aħħar",
"MessageApplicationUpdated": "Jellyfin Server ġie aġġornat",
"MessageApplicationUpdatedTo": "JellyFin Server ġie aġġornat għal {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Is-sezzjoni {0} tal-konfigurazzjoni tas-server ġiet aġġornata",
"MessageServerConfigurationUpdated": "Il-konfigurazzjoni tas-server ġiet aġġornata",
"MixedContent": "Kontenut imħallat",
"Movies": "Films",
"Music": "Mużika",
"MusicVideos": "Vidjows tal-Mużika",
"NameInstallFailed": "L-installazzjoni ta' {0} falliet",
"NameSeasonNumber": "Staġun {0}",
"NameSeasonUnknown": "Staġun Mhux Magħruf",
"NewVersionIsAvailable": "Verżjoni ġdida ta' Jellyfin Server hija disponibbli biex titniżżel.",
"NotificationOptionApplicationUpdateAvailable": "Aġġornament tal-applikazzjoni disponibbli",
"NotificationOptionCameraImageUploaded": "Immaġini tal-kamera mtella'",
"LabelRunningTimeValue": "Tul: {0}",
"NotificationOptionApplicationUpdateInstalled": "Aġġornament tal-applikazzjoni ġie installat",
"NotificationOptionAudioPlayback": "Il-playback tal-awdjo beda",
"NotificationOptionAudioPlaybackStopped": "Il-playback tal-awdjo twaqqaf",
"NotificationOptionInstallationFailed": "Installazzjoni falliet",
"NotificationOptionNewLibraryContent": "Kontenut ġdid miżjud",
"NotificationOptionPluginError": "Ħsara fil-plugin",
"NotificationOptionPluginInstalled": "Plugin installat",
"NotificationOptionPluginUninstalled": "Plugin tneħħa",
"NotificationOptionServerRestartRequired": "Meħtieġ l-istartjar mill-ġdid tas-server",
"NotificationOptionTaskFailed": "Falliment tal-kompitu skedat",
"NotificationOptionUserLockedOut": "Utent imsakkar",
"Photos": "Ritratti",
"Playlists": "Playlists",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} ġie installat",
"PluginUninstalledWithName": "{0} ġie mneħħi",
"PluginUpdatedWithName": "{0} ġie aġġornat",
"ProviderValue": "Fornitur: {0}",
"ScheduledTaskFailedWithName": "{0} falla",
"ScheduledTaskStartedWithName": "{0} beda",
"ServerNameNeedsToBeRestarted": "{0} jeħtieġ li jerġa' jinbeda",
"Songs": "Kanzunetti",
"StartupEmbyServerIsLoading": "Jellyfin Server qed jixgħel. Jekk jogħġbok erġa' pprova dalwaqt.",
"Sync": "Sinkronizza",
"System": "Sistema",
"Undefined": "Mhux Definit",
"User": "Utent",
"UserCreatedWithName": "L-utent {0} inħoloq",
"UserDeletedWithName": "L-utent {0} tħassar",
"UserDownloadingItemWithValues": "{0} qed iniżżel {1}",
"UserLockedOutWithName": "L-utent {0} ġie msakkar",
"UserOfflineFromDevice": "{0} skonnettja minn {1}",
"UserOnlineFromDevice": "{0} huwa online minn {1}",
"NotificationOptionPluginUpdateInstalled": "Aġġornament ta' plugin ġie installat",
"NotificationOptionVideoPlayback": "Il-playback tal-vidjow beda",
"NotificationOptionVideoPlaybackStopped": "Il-playback tal-vidjow waqaf",
"Shows": "Programmi",
"TvShows": "Programmi tat-TV",
"UserPolicyUpdatedWithName": "Il-policy tal-utent ġiet aġġornata għal {0}",
"UserStartedPlayingItemWithValues": "{0} qed iħaddem {1} fuq {2}",
"UserStoppedPlayingItemWithValues": "{0} waqaf iħaddem {1} fuq {2}",
"ValueHasBeenAddedToLibrary": "{0} ġie miżjud mal-librerija tal-midja tiegħek",
"ValueSpecialEpisodeName": "Speċjali - {0}",
"VersionNumber": "Verżjoni {0}",
"TasksMaintenanceCategory": "Manutenzjoni",
"TasksLibraryCategory": "Librerija",
"TasksApplicationCategory": "Applikazzjoni",
"TasksChannelsCategory": "Kanali tal-Internet",
"TaskCleanActivityLog": "Naddaf il-Logg tal-Attività",
"TaskCleanActivityLogDescription": "Iħassar l-entrati tar-reġistru tal-attività eqdem mill-età kkonfigurata.",
"TaskCleanCache": "Naddaf id-Direttorju tal-Cache",
"TaskCleanCacheDescription": "Iħassar il-fajls tal-cache li m'għadhomx meħtieġa mis-sistema.",
"TaskRefreshChapterImages": "Oħroġ l-Immaġini tal-Kapitolu",
"TaskRefreshChapterImagesDescription": "Joħloq thumbnails għal vidjows li għandhom kapitli.",
"TaskAudioNormalization": "Normalizzazzjoni Awdjo",
"TaskAudioNormalizationDescription": "Skennja fajls għal data ta' normalizzazzjoni awdjo.",
"TaskRefreshLibrary": "Skennja l-Librerija tal-Midja",
"TaskRefreshLibraryDescription": "Jiskennja l-librerija tal-midja tiegħek għal fajls ġodda u jġedded il-metadejta.",
"TaskCleanLogs": "Naddaf id-Direttorju tal-Logg",
"TaskCleanLogsDescription": "Iħassar fajls tal-logg eqdem minn {0} ijiem.",
"TaskRefreshPeople": "Aġġorna Persuni",
"TaskRefreshPeopleDescription": "Jaġġorna l-metadejta għall-atturi u d-diretturi fil-librerija tal-midja tiegħek.",
"TaskRefreshTrickplayImages": "Iġġenera Stampi Trickplay",
"TaskRefreshTrickplayImagesDescription": "Joħloq previews trickplay għal vidjows fil-libreriji attivati.",
"TaskUpdatePlugins": "Aġġorna il-Plugins",
"TaskCleanTranscode": "Naddaf id-Direttorju tat-Transcode",
"TaskCleanTranscodeDescription": "Iħassar fajls transcode eqdem minn ġurnata.",
"TaskRefreshChannels": "Aġġorna l-Kanali",
"TaskRefreshChannelsDescription": "Aġġorna l-informazzjoni tal-kanali tal-internet.",
"TaskDownloadMissingSubtitles": "Niżżel is-sottotitli nieqsa",
"TaskOptimizeDatabase": "Ottimizza d-database",
"TaskKeyframeExtractor": "Estrattur ta' Keyframes",
"TaskKeyframeExtractorDescription": "Jiġbed il-keyframes mill-fajls tal-vidjow biex joħloq playlists HLS aktar preċiżi. Dan il-kompitu jista' jdum għal żmien twil.",
"TaskCleanCollectionsAndPlaylists": "Naddaf il-kollezzjonijiet u l-playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Ineħħi oġġetti minn kollezzjonijiet u playlists li m'għadhomx jeżistu."
}

View File

@ -48,7 +48,7 @@
"Undefined": "သတ်မှတ်မထားသော", "Undefined": "သတ်မှတ်မထားသော",
"TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ", "TvShows": "တီဗီ ဇာတ်လမ်းတွဲများ",
"System": "စနစ်", "System": "စနစ်",
"Sync": "ထပ်တူကျသည်။", "Sync": "ချိန်ကိုက်မည်",
"SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ", "SubtitleDownloadFailureFromForItem": "{1} အတွက် {0} မှ စာတန်းထိုးများ ဒေါင်းလုဒ်လုပ်ခြင်း မအောင်မြင်ပါ",
"StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။", "StartupEmbyServerIsLoading": "Jellyfin ဆာဗာကို အသင့်ပြင်နေပါသည်။ ခဏနေ ထပ်စမ်းကြည့်ပါ။",
"Songs": "သီချင်းများ", "Songs": "သီချင်းများ",
@ -104,7 +104,7 @@
"HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ", "HeaderFavoriteSongs": "အကြိုက်ဆုံးသီချင်းများ",
"HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ", "HeaderFavoriteShows": "အကြိုက်ဆုံး ဇာတ်လမ်းတွဲများ",
"HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ", "HeaderFavoriteEpisodes": "အကြိုက်ဆုံး ဇာတ်လမ်းအပိုင်းများ",
"HeaderFavoriteArtists": "အကြိုက်ဆုံးအနုပညာရှင်များ", "HeaderFavoriteArtists": "အကြိုက်ဆုံး အနုပညာရှင်များ",
"HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ", "HeaderFavoriteAlbums": "အကြိုက်ဆုံး အယ်လ်ဘမ်များ",
"HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ", "HeaderContinueWatching": "ဆက်လက်ကြည့်ရှုပါ",
"HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ", "HeaderAlbumArtists": "အယ်လ်ဘမ်အနုပညာရှင်များ",
@ -120,5 +120,11 @@
"AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ", "AuthenticationSucceededWithUserName": "{0} အောင်မြင်စွာ စစ်မှန်ကြောင်း အတည်ပြုပြီးပါပြီ",
"Application": "အပလီကေးရှင်း", "Application": "အပလီကေးရှင်း",
"AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}", "AppDeviceValues": "အက်ပ်- {0}၊ စက်- {1}",
"External": "ပြင်ပ" "External": "ပြင်ပ",
"TaskKeyframeExtractorDescription": "ပိုမိုတိကျသည့် အိတ်ချ်အယ်လ်အက်စ် အစဉ်လိုက်ပြသမှုများ ဖန်တီးနိုင်ရန်အတွက် ဗီဒီယိုဖိုင်များမှ ကီးဖရိန်များကို ထုတ်နှုတ်ယူမည် ဖြစ်သည်။ ဤလုပ်ဆောင်မှုသည် အချိန်ကြာရှည်နိုင်သည်။",
"TaskCleanCollectionsAndPlaylistsDescription": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများမှ မရှိတော့သည်များကို ဖယ်ရှားမည်။",
"TaskRefreshTrickplayImages": "ထရစ်ခ်ပလေး ပုံများကို ထုတ်မည်",
"TaskKeyframeExtractor": "ကီးဖရိန်များကို ထုတ်နုတ်ခြင်း",
"TaskCleanCollectionsAndPlaylists": "စုစည်းမှုများနှင့် အစဉ်လိုက်ပြသမှုများကို ရှင်းလင်းမည်",
"HearingImpaired": "အကြားအာရုံ ချို့တဲ့သူ"
} }

View File

@ -126,5 +126,9 @@
"External": "Ekstern", "External": "Ekstern",
"HearingImpaired": "Hørselshemmet", "HearingImpaired": "Hørselshemmet",
"TaskRefreshTrickplayImages": "Generer Trickplay bilder", "TaskRefreshTrickplayImages": "Generer Trickplay bilder",
"TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker." "TaskRefreshTrickplayImagesDescription": "Oppretter trickplay-forhåndsvisninger for videoer i aktiverte biblioteker.",
"TaskCleanCollectionsAndPlaylists": "Rydd kolleksjoner og spillelister",
"TaskAudioNormalization": "Lyd Normalisering",
"TaskAudioNormalizationDescription": "Skan filer for lyd normaliserende data",
"TaskCleanCollectionsAndPlaylistsDescription": "Fjerner elementer fra kolleksjoner og spillelister som ikke lengere finnes"
} }

View File

@ -11,7 +11,7 @@
"Collections": "Collecties", "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 aanmeldpoging van {0}",
"Favorites": "Favorieten", "Favorites": "Favorieten",
"Folders": "Mappen", "Folders": "Mappen",
"Genres": "Genres", "Genres": "Genres",
@ -124,7 +124,11 @@
"TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.", "TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.",
"TaskKeyframeExtractor": "Keyframes uitpakken", "TaskKeyframeExtractor": "Keyframes uitpakken",
"External": "Extern", "External": "Extern",
"HearingImpaired": "Slechthorend", "HearingImpaired": "Slechthorenden",
"TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren", "TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren",
"TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld." "TaskRefreshTrickplayImagesDescription": "Creëert trickplay-voorvertoningen voor video's in bibliotheken waarvoor dit is ingeschakeld.",
"TaskCleanCollectionsAndPlaylists": "Collecties en afspeellijsten opruimen",
"TaskCleanCollectionsAndPlaylistsDescription": "Verwijdert niet langer bestaande items uit collecties en afspeellijsten.",
"TaskAudioNormalization": "Geluidsnormalisatie",
"TaskAudioNormalizationDescription": "Scant bestanden op gegevens voor geluidsnormalisatie."
} }

View File

@ -118,5 +118,6 @@
"Undefined": "Udefinert", "Undefined": "Udefinert",
"Forced": "Tvungen", "Forced": "Tvungen",
"Default": "Standard", "Default": "Standard",
"External": "Ekstern" "External": "Ekstern",
"HearingImpaired": "Nedsett høyrsel"
} }

View File

@ -104,7 +104,7 @@
"Forced": "ਮਜਬੂਰ", "Forced": "ਮਜਬੂਰ",
"Folders": "ਫੋਲਡਰ", "Folders": "ਫੋਲਡਰ",
"Favorites": "ਮਨਪਸੰਦ", "Favorites": "ਮਨਪਸੰਦ",
"FailedLoginAttemptWithUserName": "{0} ਤੋਂ ਲਾਗਇਨ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ", "FailedLoginAttemptWithUserName": "{0} ਤੋਂ ਲਾਗਇਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਫੇਲ ਹੋਈ",
"DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ", "DeviceOnlineWithName": "{0} ਜੁੜਿਆ ਹੋਇਆ ਹੈ",
"DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ", "DeviceOfflineWithName": "{0} ਡਿਸਕਨੈਕਟ ਹੋ ਗਿਆ ਹੈ",
"Default": "ਡਿਫੌਲਟ", "Default": "ਡਿਫੌਲਟ",
@ -119,5 +119,6 @@
"AppDeviceValues": "ਐਪ: {0}, ਜੰਤਰ: {1}", "AppDeviceValues": "ਐਪ: {0}, ਜੰਤਰ: {1}",
"Albums": "ਐਲਬਮਾਂ", "Albums": "ਐਲਬਮਾਂ",
"TaskOptimizeDatabase": "ਡਾਟਾਬੇਸ ਅਨੁਕੂਲ ਬਣਾਓ", "TaskOptimizeDatabase": "ਡਾਟਾਬੇਸ ਅਨੁਕੂਲ ਬਣਾਓ",
"External": "ਬਾਹਰੀ" "External": "ਬਾਹਰੀ",
"HearingImpaired": "ਸੁਨਣ ਵਿਚ ਕਮਜ਼ੋਰ"
} }

View File

@ -11,7 +11,7 @@
"Collections": "Kolekcje", "Collections": "Kolekcje",
"DeviceOfflineWithName": "{0} został rozłączony", "DeviceOfflineWithName": "{0} został rozłączony",
"DeviceOnlineWithName": "{0} połączył się", "DeviceOnlineWithName": "{0} połączył się",
"FailedLoginAttemptWithUserName": "Próba logowania przez {0} zakończona niepowodzeniem", "FailedLoginAttemptWithUserName": "Nieudana próba logowania przez {0}",
"Favorites": "Ulubione", "Favorites": "Ulubione",
"Folders": "Foldery", "Folders": "Foldery",
"Genres": "Gatunki", "Genres": "Gatunki",
@ -98,8 +98,8 @@
"TaskRefreshChannels": "Odśwież kanały", "TaskRefreshChannels": "Odśwież kanały",
"TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.", "TaskCleanTranscodeDescription": "Usuwa transkodowane pliki starsze niż 1 dzień.",
"TaskCleanTranscode": "Wyczyść folder transkodowania", "TaskCleanTranscode": "Wyczyść folder transkodowania",
"TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje dla pluginów, które są skonfigurowane do automatycznej aktualizacji.", "TaskUpdatePluginsDescription": "Pobiera i instaluje aktualizacje wtyczek, które są skonfigurowane do automatycznej aktualizacji.",
"TaskUpdatePlugins": "Aktualizuj pluginy", "TaskUpdatePlugins": "Aktualizuj wtyczki",
"TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.", "TaskRefreshPeopleDescription": "Odświeża metadane o aktorów i reżyserów w Twojej bibliotece mediów.",
"TaskRefreshPeople": "Odśwież obsadę", "TaskRefreshPeople": "Odśwież obsadę",
"TaskCleanLogsDescription": "Kasuje pliki logów starsze niż {0} dni.", "TaskCleanLogsDescription": "Kasuje pliki logów starsze niż {0} dni.",
@ -126,5 +126,9 @@
"TaskKeyframeExtractor": "Ekstraktor klatek kluczowych", "TaskKeyframeExtractor": "Ekstraktor klatek kluczowych",
"HearingImpaired": "Niedosłyszący", "HearingImpaired": "Niedosłyszący",
"TaskRefreshTrickplayImages": "Generuj obrazy trickplay", "TaskRefreshTrickplayImages": "Generuj obrazy trickplay",
"TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach." "TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach.",
"TaskCleanCollectionsAndPlaylistsDescription": "Usuwa elementy z kolekcji i list odtwarzania, które już nie istnieją.",
"TaskCleanCollectionsAndPlaylists": "Oczyść kolekcje i listy odtwarzania",
"TaskAudioNormalization": "Normalizacja dźwięku",
"TaskAudioNormalizationDescription": "Skanuje pliki w poszukiwaniu danych normalizacji dźwięku."
} }

View File

@ -111,7 +111,7 @@
"TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.", "TaskCleanCacheDescription": "Deletar arquivos temporários que não são mais necessários para o sistema.",
"TaskCleanCache": "Limpar Arquivos Temporários", "TaskCleanCache": "Limpar Arquivos Temporários",
"TasksChannelsCategory": "Canais da Internet", "TasksChannelsCategory": "Canais da Internet",
"TasksApplicationCategory": "Aplicativo", "TasksApplicationCategory": "Aplicação",
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manutenção", "TasksMaintenanceCategory": "Manutenção",
"TaskCleanActivityLogDescription": "Apaga o registro de atividades mais antigo que a idade configurada.", "TaskCleanActivityLogDescription": "Apaga o registro de atividades mais antigo que a idade configurada.",
@ -126,5 +126,9 @@
"External": "Externo", "External": "Externo",
"HearingImpaired": "Deficiência Auditiva", "HearingImpaired": "Deficiência Auditiva",
"TaskRefreshTrickplayImages": "Gerar imagens Trickplay", "TaskRefreshTrickplayImages": "Gerar imagens Trickplay",
"TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado." "TaskRefreshTrickplayImagesDescription": "Cria prévias Trickplay para vídeos em bibliotecas em que o recurso está habilitado.",
"TaskCleanCollectionsAndPlaylists": "Limpe coleções e playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e playlists que não existem mais.",
"TaskAudioNormalization": "Normalização de áudio",
"TaskAudioNormalizationDescription": "Examina os ficheiros em busca de dados de normalização de áudio."
} }

View File

@ -126,5 +126,9 @@
"External": "Externo", "External": "Externo",
"HearingImpaired": "Surdo", "HearingImpaired": "Surdo",
"TaskRefreshTrickplayImages": "Gerar imagens de truques", "TaskRefreshTrickplayImages": "Gerar imagens de truques",
"TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas." "TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas.",
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
"TaskAudioNormalization": "Normalização de áudio"
} }

View File

@ -125,5 +125,9 @@
"TaskKeyframeExtractor": "Extrator de quadro-chave", "TaskKeyframeExtractor": "Extrator de quadro-chave",
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.", "TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.",
"TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo", "TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo",
"TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas." "TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas.",
"TaskCleanCollectionsAndPlaylistsDescription": "Remove itens de coleções e listas de reprodução que já não existem.",
"TaskCleanCollectionsAndPlaylists": "Limpar coleções e listas de reprodução",
"TaskAudioNormalizationDescription": "Analisa os ficheiros para obter dados de normalização de áudio.",
"TaskAudioNormalization": "Normalização de áudio"
} }

View File

@ -78,7 +78,7 @@
"Genres": "Genuri", "Genres": "Genuri",
"Folders": "Dosare", "Folders": "Dosare",
"Favorites": "Favorite", "Favorites": "Favorite",
"FailedLoginAttemptWithUserName": "Încercare de conectare nereușită de la {0}", "FailedLoginAttemptWithUserName": "Încercare de conectare eșuată pentru {0}",
"DeviceOnlineWithName": "{0} este conectat", "DeviceOnlineWithName": "{0} este conectat",
"DeviceOfflineWithName": "{0} s-a deconectat", "DeviceOfflineWithName": "{0} s-a deconectat",
"Collections": "Colecții", "Collections": "Colecții",

View File

@ -11,7 +11,7 @@
"Collections": "Коллекции", "Collections": "Коллекции",
"DeviceOfflineWithName": "{0} - отключено", "DeviceOfflineWithName": "{0} - отключено",
"DeviceOnlineWithName": "{0} - подключено", "DeviceOnlineWithName": "{0} - подключено",
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна", "FailedLoginAttemptWithUserName": "Неудачная попытка входа с {0}",
"Favorites": "Избранное", "Favorites": "Избранное",
"Folders": "Папки", "Folders": "Папки",
"Genres": "Жанры", "Genres": "Жанры",
@ -31,7 +31,7 @@
"ItemRemovedWithName": "{0} - изъято из медиатеки", "ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}", "LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Длительность: {0}", "LabelRunningTimeValue": "Длительность: {0}",
"Latest": "Последние добавленные", "Latest": "Последние",
"MessageApplicationUpdated": "Jellyfin Server был обновлён", "MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}", "MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена", "MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
@ -126,5 +126,9 @@
"External": "Внешние", "External": "Внешние",
"HearingImpaired": "Для слабослышащих", "HearingImpaired": "Для слабослышащих",
"TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay", "TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay",
"TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена." "TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена.",
"TaskCleanCollectionsAndPlaylists": "Очистка коллекций и списков воспроизведения",
"TaskCleanCollectionsAndPlaylistsDescription": "Удаляет элементы из коллекций и списков воспроизведения, которые больше не существуют.",
"TaskAudioNormalization": "Нормализация звука",
"TaskAudioNormalizationDescription": "Сканирует файлы на наличие данных о нормализации звука."
} }

View File

@ -126,5 +126,9 @@
"External": "Externé", "External": "Externé",
"HearingImpaired": "Sluchovo postihnutí", "HearingImpaired": "Sluchovo postihnutí",
"TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay", "TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay",
"TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach." "TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach.",
"TaskCleanCollectionsAndPlaylists": "Vyčistiť kolekcie a playlisty",
"TaskCleanCollectionsAndPlaylistsDescription": "Odstráni položky z kolekcií a playlistov, ktoré už neexistujú.",
"TaskAudioNormalization": "Normalizácia zvuku",
"TaskAudioNormalizationDescription": "Skenovať súbory za účelom normalizácie zvuku."
} }

View File

@ -126,5 +126,9 @@
"External": "Extern", "External": "Extern",
"HearingImpaired": "Hörselskadad", "HearingImpaired": "Hörselskadad",
"TaskRefreshTrickplayImages": "Generera Trickplay-bilder", "TaskRefreshTrickplayImages": "Generera Trickplay-bilder",
"TaskRefreshTrickplayImagesDescription": "Skapar trickplay-förhandsvisningar för videor i aktiverade bibliotek." "TaskRefreshTrickplayImagesDescription": "Skapar trickplay-förhandsvisningar för videor i aktiverade bibliotek.",
"TaskCleanCollectionsAndPlaylists": "Rensa upp samlingar och spellistor",
"TaskAudioNormalization": "Ljudnormalisering",
"TaskCleanCollectionsAndPlaylistsDescription": "Tar bort objekt från samlingar och spellistor som inte längre finns.",
"TaskAudioNormalizationDescription": "Skannar filer för ljudnormaliseringsdata."
} }

View File

@ -125,5 +125,9 @@
"External": "வெளி", "External": "வெளி",
"HearingImpaired": "செவித்திறன் குறைபாடுடையவர்", "HearingImpaired": "செவித்திறன் குறைபாடுடையவர்",
"TaskRefreshTrickplayImages": "முன்னோட்ட படங்களை உருவாக்கு", "TaskRefreshTrickplayImages": "முன்னோட்ட படங்களை உருவாக்கு",
"TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்." "TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்.",
"TaskCleanCollectionsAndPlaylists": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களை சுத்தம் செய்யவும்",
"TaskCleanCollectionsAndPlaylistsDescription": "சேகரிப்புகள் மற்றும் பிளேலிஸ்ட்களில் இருந்து உருப்படிகளை நீக்குகிறது.",
"TaskAudioNormalization": "ஆடியோ இயல்பாக்கம்",
"TaskAudioNormalizationDescription": "ஆடியோ இயல்பாக்குதல் தரவுக்காக கோப்புகளை ஸ்கேன் செய்கிறது."
} }

View File

@ -123,5 +123,7 @@
"External": "ภายนอก", "External": "ภายนอก",
"HearingImpaired": "บกพร่องทางการได้ยิน", "HearingImpaired": "บกพร่องทางการได้ยิน",
"TaskKeyframeExtractor": "ตัวแยกคีย์เฟรม", "TaskKeyframeExtractor": "ตัวแยกคีย์เฟรม",
"TaskKeyframeExtractorDescription": "แยกคีย์เฟรมจากไฟล์วีดีโอเพื่อสร้างรายการ HLS ให้ถูกต้อง. กระบวนการนี้อาจใช้ระยะเวลานาน" "TaskKeyframeExtractorDescription": "แยกคีย์เฟรมจากไฟล์วีดีโอเพื่อสร้างรายการ HLS ให้ถูกต้อง. กระบวนการนี้อาจใช้ระยะเวลานาน",
"TaskRefreshTrickplayImages": "สร้างไฟล์รูปภาพสำหรับ Trickplay",
"TaskRefreshTrickplayImagesDescription": "สร้างภาพตัวอย่างของวีดีโอในคลังที่เปิดใช้งาน Trickplay"
} }

View File

@ -11,7 +11,7 @@
"Collections": "Koleksiyonlar", "Collections": "Koleksiyonlar",
"DeviceOfflineWithName": "{0} bağlantısı kesildi", "DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı", "DeviceOnlineWithName": "{0} bağlı",
"FailedLoginAttemptWithUserName": "{0} kullanıcısının giriş denemesi başarısız oldu", "FailedLoginAttemptWithUserName": "{0} kullanıcısının başarısız oturum açma girişimi",
"Favorites": "Favoriler", "Favorites": "Favoriler",
"Folders": "Klasörler", "Folders": "Klasörler",
"Genres": "Türler", "Genres": "Türler",
@ -126,5 +126,9 @@
"External": "Harici", "External": "Harici",
"HearingImpaired": "Duyma Engelli", "HearingImpaired": "Duyma Engelli",
"TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur", "TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur",
"TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur." "TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur.",
"TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.",
"TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin",
"TaskAudioNormalizationDescription": "Ses normalleştirme verileri için dosyaları tarar.",
"TaskAudioNormalization": "Ses Normalleştirme"
} }

View File

@ -125,5 +125,9 @@
"External": "Зовнішній", "External": "Зовнішній",
"HearingImpaired": "З порушеннями слуху", "HearingImpaired": "З порушеннями слуху",
"TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.", "TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.",
"TaskRefreshTrickplayImages": "Створити Trickplay-зображення" "TaskRefreshTrickplayImages": "Створити Trickplay-зображення",
"TaskCleanCollectionsAndPlaylists": "Очистити колекції і списки відтворення",
"TaskCleanCollectionsAndPlaylistsDescription": "Видаляє елементи з колекцій і списків відтворення, які більше не існують.",
"TaskAudioNormalizationDescription": "Сканує файли на наявність даних для нормалізації звуку.",
"TaskAudioNormalization": "Нормалізація аудіо"
} }

View File

@ -8,5 +8,20 @@
"Channels": "Kanallar", "Channels": "Kanallar",
"Books": "Kitoblar", "Books": "Kitoblar",
"Artists": "Ijrochilar", "Artists": "Ijrochilar",
"Albums": "Albomlar" "Albums": "Albomlar",
"AuthenticationSucceededWithUserName": "{0} muvaffaqiyatli tasdiqlandi",
"AppDeviceValues": "Ilova: {0}, Qurilma: {1}",
"Application": "Ilova",
"CameraImageUploadedFrom": "{0}dan yangi kamera rasmi yuklandi",
"DeviceOnlineWithName": "{0} ulangan",
"ItemRemovedWithName": "{0} kutbxonadan o'chirildi",
"External": "Tashqi",
"FailedLoginAttemptWithUserName": "Muvafaqiyatsiz kirishlar soni {0}",
"Forced": "Majburiy",
"ChapterNameValue": "{0}chi bo'lim",
"DeviceOfflineWithName": "{0} aloqa uzildi",
"HeaderLiveTV": "Jonli TV",
"HeaderNextUp": "Keyingisi",
"ItemAddedWithName": "{0} kutbxonaga qo'shildi",
"LabelIpAddressValue": "IP manzil: {0}"
} }

View File

@ -103,11 +103,11 @@
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích", "HeaderFavoriteAlbums": "Album Ưa Thích",
"FailedLoginAttemptWithUserName": "Đăng nhập không thành công thử từ {0}", "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập không thành công từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối", "DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối",
"ChapterNameValue": "Phân Cảnh {0}", "ChapterNameValue": "Phân Cảnh {0}",
"Channels": "Các Kênh", "Channels": "Kênh",
"CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}", "CameraImageUploadedFrom": "Một hình ảnh máy ảnh mới đã được tải lên từ {0}",
"Books": "Sách", "Books": "Sách",
"AuthenticationSucceededWithUserName": "{0} xác thực thành công", "AuthenticationSucceededWithUserName": "{0} xác thực thành công",
@ -125,5 +125,9 @@
"External": "Bên ngoài", "External": "Bên ngoài",
"HearingImpaired": "Khiếm Thính", "HearingImpaired": "Khiếm Thính",
"TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay", "TaskRefreshTrickplayImages": "Tạo Ảnh Xem Trước Trickplay",
"TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật." "TaskRefreshTrickplayImagesDescription": "Tạo bản xem trước trịckplay cho video trong thư viện đã bật.",
"TaskCleanCollectionsAndPlaylists": "Dọn dẹp bộ sưu tập và danh sách phát",
"TaskCleanCollectionsAndPlaylistsDescription": "Xóa các mục khỏi bộ sưu tập và danh sách phát không còn tồn tại.",
"TaskAudioNormalization": "Chuẩn Hóa Âm Thanh",
"TaskAudioNormalizationDescription": "Quét tập tin để tìm dữ liệu chuẩn hóa âm thanh."
} }

View File

@ -11,7 +11,7 @@
"Collections": "合集", "Collections": "合集",
"DeviceOfflineWithName": "{0} 已断开", "DeviceOfflineWithName": "{0} 已断开",
"DeviceOnlineWithName": "{0} 已连接", "DeviceOnlineWithName": "{0} 已连接",
"FailedLoginAttemptWithUserName": "从 {0} 尝试登录失败", "FailedLoginAttemptWithUserName": "来自 {0} 的登录尝试失败",
"Favorites": "我的最爱", "Favorites": "我的最爱",
"Folders": "文件夹", "Folders": "文件夹",
"Genres": "类型", "Genres": "类型",
@ -126,5 +126,9 @@
"External": "外部", "External": "外部",
"HearingImpaired": "听力障碍", "HearingImpaired": "听力障碍",
"TaskRefreshTrickplayImages": "生成时间轴缩略图", "TaskRefreshTrickplayImages": "生成时间轴缩略图",
"TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。" "TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。",
"TaskCleanCollectionsAndPlaylists": "清理合集和播放列表",
"TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。",
"TaskAudioNormalization": "音频标准化",
"TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。"
} }

View File

@ -1,25 +1,25 @@
{ {
"Albums": "專輯", "Albums": "專輯",
"AppDeviceValues": "App{0},裝置:{1}", "AppDeviceValues": "應用程式{0},裝置:{1}",
"Application": "應用程式", "Application": "應用程式",
"Artists": "演出者", "Artists": "藝人",
"AuthenticationSucceededWithUserName": "{0} 成功授權", "AuthenticationSucceededWithUserName": "成功授權 {0}",
"Books": "書", "Books": "",
"CameraImageUploadedFrom": "{0} 已經成功上傳一張相片", "CameraImageUploadedFrom": "已從 {0} 成功上傳一張相片",
"Channels": "頻道", "Channels": "頻道",
"ChapterNameValue": "章節 {0}", "ChapterNameValue": "章節 {0}",
"Collections": "合輯", "Collections": "系列",
"DeviceOfflineWithName": "{0} 已經斷線", "DeviceOfflineWithName": "{0} 已中斷連接",
"DeviceOnlineWithName": "{0} 已經連線", "DeviceOnlineWithName": "{0} 已連接",
"FailedLoginAttemptWithUserName": "來自使用者 {0} 的失敗登入", "FailedLoginAttemptWithUserName": "來自使用者 {0} 的登入失敗嘗試",
"Favorites": "我的最愛", "Favorites": "我的最愛",
"Folders": "資料夾", "Folders": "資料夾",
"Genres": "風格", "Genres": "風格",
"HeaderAlbumArtists": "專輯演出者", "HeaderAlbumArtists": "專輯演出者",
"HeaderContinueWatching": "繼續觀", "HeaderContinueWatching": "繼續觀",
"HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者", "HeaderFavoriteArtists": "最愛藝人",
"HeaderFavoriteEpisodes": "最愛集", "HeaderFavoriteEpisodes": "最愛集",
"HeaderFavoriteShows": "最愛節目", "HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲", "HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播", "HeaderLiveTV": "電視直播",
@ -30,8 +30,8 @@
"LabelIpAddressValue": "IP 位址:{0}", "LabelIpAddressValue": "IP 位址:{0}",
"LabelRunningTimeValue": "運行時間:{0}", "LabelRunningTimeValue": "運行時間:{0}",
"Latest": "最新", "Latest": "最新",
"MessageApplicationUpdated": "Jellyfin Server 已經更新", "MessageApplicationUpdated": "Jellyfin 伺服器已經更新",
"MessageApplicationUpdatedTo": "Jellyfin Server 已經更新至 {0}", "MessageApplicationUpdatedTo": "Jellyfin 伺服器已經更新至 {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新", "MessageNamedServerConfigurationUpdatedWithValue": "伺服器設定 {0} 部分已經更新",
"MessageServerConfigurationUpdated": "伺服器設定已經更新", "MessageServerConfigurationUpdated": "伺服器設定已經更新",
"MixedContent": "混合內容", "MixedContent": "混合內容",
@ -41,7 +41,7 @@
"NameInstallFailed": "{0} 安裝失敗", "NameInstallFailed": "{0} 安裝失敗",
"NameSeasonNumber": "第 {0} 季", "NameSeasonNumber": "第 {0} 季",
"NameSeasonUnknown": "未知季數", "NameSeasonUnknown": "未知季數",
"NewVersionIsAvailable": "新版本的 Jellyfin Server 已經可供下載。", "NewVersionIsAvailable": "新版本的 Jellyfin 伺服器已經可供下載。",
"NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新", "NotificationOptionApplicationUpdateAvailable": "有可用的應用程式更新",
"NotificationOptionApplicationUpdateInstalled": "應用程式更新已安裝", "NotificationOptionApplicationUpdateInstalled": "應用程式更新已安裝",
"NotificationOptionAudioPlayback": "音訊播放已開始", "NotificationOptionAudioPlayback": "音訊播放已開始",
@ -49,52 +49,52 @@
"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 Server 載入中,請稍後再試。", "StartupEmbyServerIsLoading": "Jellyfin 伺服器載入中,請稍後再試。",
"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}",
"HeaderRecordingGroups": "錄製組", "HeaderRecordingGroups": "錄製組",
"Inherit": "繼承", "Inherit": "繼承",
"SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕", "SubtitleDownloadFailureFromForItem": "無法從 {0} 下載 {1} 的字幕",
"TaskDownloadMissingSubtitlesDescription": "透過媒體資訊從網路上搜尋遺失的字幕。", "TaskDownloadMissingSubtitlesDescription": "透過媒體資訊從網路上搜尋遺失的字幕。",
"TaskDownloadMissingSubtitles": "下載遺失的字幕", "TaskDownloadMissingSubtitles": "下載遺失的字幕",
"TaskRefreshChannels": "重新整理頻道", "TaskRefreshChannels": "重新整理頻道",
"TaskUpdatePlugins": "更新附加元件", "TaskUpdatePlugins": "更新擴充功能",
"TaskRefreshPeople": "更新人物", "TaskRefreshPeople": "更新人物",
"TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。", "TaskCleanLogsDescription": "刪除超過 {0} 天的日誌文件。",
"TaskCleanLogs": "清空日誌資料夾", "TaskCleanLogs": "清空日誌資料夾",
@ -105,9 +105,9 @@
"TaskCleanCache": "清除快取資料夾", "TaskCleanCache": "清除快取資料夾",
"TasksLibraryCategory": "媒體庫", "TasksLibraryCategory": "媒體庫",
"TaskRefreshChannelsDescription": "重新整理網路頻道資料。", "TaskRefreshChannelsDescription": "重新整理網路頻道資料。",
"TaskCleanTranscodeDescription": "刪除超過一天的轉。", "TaskCleanTranscodeDescription": "刪除超過一天的轉檔。",
"TaskCleanTranscode": "清除轉資料夾", "TaskCleanTranscode": "清除轉資料夾",
"TaskUpdatePluginsDescription": "為已設置為自動更新的附加元件下載並安裝更新。", "TaskUpdatePluginsDescription": "下載並更新已啟用自動更新的擴充功能。",
"TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的資訊。", "TaskRefreshPeopleDescription": "更新媒體庫中演員和導演的資訊。",
"TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。", "TaskRefreshChapterImagesDescription": "為有章節的影片建立縮圖。",
"TasksChannelsCategory": "網路頻道", "TasksChannelsCategory": "網路頻道",
@ -125,5 +125,9 @@
"External": "外部", "External": "外部",
"HearingImpaired": "聽力障礙", "HearingImpaired": "聽力障礙",
"TaskRefreshTrickplayImages": "生成快轉縮圖", "TaskRefreshTrickplayImages": "生成快轉縮圖",
"TaskRefreshTrickplayImagesDescription": "為啟用此設定的媒體庫生成快轉縮圖。" "TaskRefreshTrickplayImagesDescription": "為啟用快轉縮圖的媒體庫生成快轉縮圖。",
"TaskCleanCollectionsAndPlaylists": "清理系列和播放清單",
"TaskCleanCollectionsAndPlaylistsDescription": "清理系列和播放清單中已不存在的項目。",
"TaskAudioNormalization": "音量標準化",
"TaskAudioNormalizationDescription": "掃描文件以找出音量標準化資料。"
} }

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