Compare commits

..

1 Commits

Author SHA1 Message Date
Andrew Rabert
3d0b84ce48 ci: use workflow_run pattern for PR workflows 2026-02-01 15:28:27 -05:00
603 changed files with 9918 additions and 44441 deletions

View File

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

View File

@@ -1,31 +1,17 @@
{ {
"name": "Development Jellyfin Server", "name": "Development Jellyfin Server",
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0-noble", "image": "mcr.microsoft.com/devcontainers/dotnet:9.0-bookworm",
"service": "app", "service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// restores nuget packages, installs the dotnet workloads and installs the dev https certificate // restores nuget packages, installs the dotnet workloads and installs the dev https certificate
"postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"", "postStartCommand": "sudo dotnet restore; sudo dotnet workload update; sudo dotnet dev-certs https --trust; sudo bash \"./.devcontainer/install-ffmpeg.sh\"",
// The previous way of installing extensions via the vs command dont work on selfhosted devcontainers // reads the extensions list and installs them
"customizations": { "postAttachCommand": "cat .vscode/extensions.json | jq -r .recommendations[] | xargs -n 1 code --install-extension",
"vscode": {
"extensions": [
"ms-dotnettools.csharp",
"editorconfig.editorconfig",
"github.vscode-github-actions",
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csdevkit",
"alexcvzz.vscode-sqlite",
"streetsidesoftware.code-spell-checker",
"eamodio.gitlens",
"redhat.vscode-xml"
]
}
},
"features": { "features": {
"ghcr.io/devcontainers/features/dotnet:2": { "ghcr.io/devcontainers/features/dotnet:2": {
"version": "none", "version": "none",
"dotnetRuntimeVersions": "10.0", "dotnetRuntimeVersions": "9.0",
"aspNetCoreRuntimeVersions": "10.0" "aspNetCoreRuntimeVersions": "9.0"
}, },
"ghcr.io/devcontainers-extra/features/apt-packages:1": { "ghcr.io/devcontainers-extra/features/apt-packages:1": {
"preserve_apt_list": false, "preserve_apt_list": false,

View File

@@ -87,9 +87,13 @@ body:
label: Jellyfin Server version label: Jellyfin Server version
description: What version of Jellyfin are you using? description: What version of Jellyfin are you using?
options: options:
- 10.11.8
- 10.11.7
- 10.11.6 - 10.11.6
- 10.11.5
- 10.11.4
- 10.11.3
- 10.11.2
- 10.11.1
- 10.11.0
- Master - Master
- Unstable - Unstable
- Older* - Older*

View File

@@ -8,10 +8,6 @@ on:
schedule: schedule:
- cron: '24 2 * * 4' - cron: '24 2 * * 4'
permissions:
contents: read
security-events: write
jobs: jobs:
analyze: analyze:
name: Analyze name: Analyze
@@ -27,18 +23,18 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET - name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with: with:
dotnet-version: '10.0.x' dotnet-version: '10.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0

55
.github/workflows/ci-compat-build.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: ABI Compatibility Build
on:
pull_request:
permissions: {}
jobs:
abi-head:
name: ABI - HEAD
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.x'
- name: Build
run: dotnet build Jellyfin.Server -o ./out
- name: Upload Head
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: abi-head
retention-days: 1
if-no-files-found: error
path: out/
abi-base:
name: ABI - BASE
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: Setup .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.x'
- name: Build
run: dotnet build Jellyfin.Server -o ./out
- name: Upload Base
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: abi-base
retention-days: 1
if-no-files-found: error
path: out/

View File

@@ -1,100 +1,37 @@
name: ABI Compatibility name: ABI Compatibility
on: on:
pull_request: workflow_run:
workflows: ["ABI Compatibility Build"]
types: [completed]
permissions: {} permissions: {}
jobs: jobs:
abi-head:
name: ABI - HEAD
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
- name: Build
run: |
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: abi-head
retention-days: 14
if-no-files-found: error
path: out/
abi-base:
name: ABI - BASE
if: ${{ github.base_ref != '' }}
runs-on: ubuntu-latest
permissions: read-all
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
- name: Checkout common ancestor
env:
HEAD_REF: ${{ github.head_ref }}
run: |
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
git checkout --progress --force $ANCESTOR_REF
- name: Build
run: |
dotnet build Jellyfin.Server -o ./out
- name: Upload Head
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: abi-base
retention-days: 14
if-no-files-found: error
path: out/
abi-diff: abi-diff:
permissions: permissions:
pull-requests: write # to create or update comment (peter-evans/create-or-update-comment) pull-requests: write
name: ABI - Difference name: ABI - Difference
if: ${{ github.event_name == 'pull_request' }} if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- abi-head
- abi-base
steps: steps:
- name: Download abi-head - name: Download abi-head
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with: with:
name: abi-head name: abi-head
path: abi-head path: abi-head
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download abi-base - name: Download abi-base
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with: with:
name: abi-base name: abi-base
path: abi-base path: abi-base
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup ApiCompat - name: Setup ApiCompat
run: | run: |
@@ -118,7 +55,7 @@ jobs:
uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0 uses: peter-evans/find-comment@b30e6a3c0ed37e7c023ccd3f1db5c6c0b0c23aad # v4.0.0
id: find-comment id: find-comment
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
direction: last direction: last
body-includes: abi-diff-workflow-comment body-includes: abi-diff-workflow-comment
@@ -126,7 +63,7 @@ jobs:
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
if: ${{ steps.diff.outputs.body != '' }} if: ${{ steps.diff.outputs.body != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace edit-mode: replace
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
@@ -145,7 +82,7 @@ jobs:
uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0 uses: peter-evans/create-or-update-comment@e8674b075228eee787fea43ef493e45ece1004c9 # v5.0.0
if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }} if: ${{ steps.diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
with: with:
issue-number: ${{ github.event.pull_request.number }} issue-number: ${{ github.event.workflow_run.pull_requests[0].number }}
comment-id: ${{ steps.find-comment.outputs.comment-id }} comment-id: ${{ steps.find-comment.outputs.comment-id }}
edit-mode: replace edit-mode: replace
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}

55
.github/workflows/ci-openapi-build.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: OpenAPI Build
on:
pull_request:
permissions: {}
jobs:
openapi-head:
name: OpenAPI - HEAD
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: openapi-head
retention-days: 1
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
openapi-base:
name: OpenAPI - BASE
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.base.sha }}
- name: Setup .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: openapi-base
retention-days: 1
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json

View File

@@ -1,35 +1,91 @@
name: OpenAPI Publish name: OpenAPI
on: on:
push: push:
branches: branches:
- master - master
tags: tags:
- 'v*' - 'v*'
workflow_run:
workflows: ["OpenAPI Build"]
types: [completed]
permissions: {}
jobs: jobs:
publish-openapi: openapi-head:
name: OpenAPI - Publish Artifact name: OpenAPI - HEAD
uses: ./.github/workflows/openapi-generate.yml if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup .NET
uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with:
dotnet-version: '10.0.x'
- name: Generate openapi.json
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
- name: Upload openapi.json
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: openapi-head
retention-days: 14
if-no-files-found: error
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
openapi-diff:
permissions: permissions:
contents: read pull-requests: write
with:
ref: ${{ github.sha }} name: OpenAPI - Difference
repository: ${{ github.repository }} if: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }}
artifact: openapi-head runs-on: ubuntu-latest
steps:
- name: Download openapi-head
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: openapi-head
path: openapi-head
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Download openapi-base
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
name: openapi-base
path: openapi-base
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Detect OpenAPI changes
id: openapi-diff
uses: jellyfin/openapi-diff-action@9274f6bda9d01ab091942a4a8334baa53692e8a4 # v1.0.0
with:
old-spec: openapi-base/openapi.json
new-spec: openapi-head/openapi.json
markdown: openapi-changelog.md
add-pr-comment: true
github-token: ${{ secrets.GITHUB_TOKEN }}
pr-number: ${{ github.event.workflow_run.pull_requests[0].number }}
publish-unstable: publish-unstable:
name: OpenAPI - Publish Unstable Spec name: OpenAPI - Publish Unstable Spec
if: ${{ github.event_name != 'pull_request' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }} if: ${{ github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- publish-openapi - openapi-head
steps: steps:
- name: Set unstable dated version - name: Set unstable dated version
id: version id: version
run: |- run: |-
echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with: with:
name: openapi-head name: openapi-head
path: openapi-head path: openapi-head
@@ -80,17 +136,17 @@ jobs:
publish-stable: publish-stable:
name: OpenAPI - Publish Stable Spec name: OpenAPI - Publish Stable Spec
if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }} if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- publish-openapi - openapi-head
steps: steps:
- name: Set version number - name: Set version number
id: version id: version
run: |- run: |-
echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
- name: Download openapi-head - name: Download openapi-head
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with: with:
name: openapi-head name: openapi-head
path: openapi-head path: openapi-head

View File

@@ -22,7 +22,7 @@ jobs:
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 - uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
with: with:
dotnet-version: ${{ env.SDK_VERSION }} dotnet-version: ${{ env.SDK_VERSION }}
@@ -35,7 +35,7 @@ jobs:
--verbosity minimal --verbosity minimal
- name: Merge code coverage results - name: Merge code coverage results
uses: danielpalme/ReportGenerator-GitHub-Action@049f7ec958c672fd31d5cc1cb01622dc8d2e23ab # v5.5.10 uses: danielpalme/ReportGenerator-GitHub-Action@ee0ae774f6d3afedcbd1683c1ab21b83670bdf8e # v5.5.1
with: with:
reports: "**/coverage.cobertura.xml" reports: "**/coverage.cobertura.xml"
targetdir: "merged/" targetdir: "merged/"

View File

@@ -4,7 +4,7 @@ on:
types: types:
- created - created
- edited - edited
pull_request: pull_request_target:
types: types:
- labeled - labeled
- synchronize - synchronize
@@ -36,7 +36,7 @@ jobs:
rename: rename:
name: Rename name: Rename
if: contains(github.event.comment.body, '@jellyfin-bot rename') if: contains(github.event.comment.body, '@jellyfin-bot rename') && github.event.comment.author_association == 'MEMBER'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: pull in script - name: pull in script

View File

@@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true ascending: true

View File

@@ -1,44 +0,0 @@
name: OpenAPI Generate
on:
workflow_call:
inputs:
ref:
required: true
type: string
repository:
required: true
type: string
artifact:
required: true
type: string
permissions:
contents: read
jobs:
main:
name: Main
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ inputs.ref }}
repository: ${{ inputs.repository }}
- name: Configure .NET
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
dotnet-version: '10.0.x'
- name: Create File
run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter Jellyfin.Server.Integration.Tests.OpenApiSpecTests
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: ${{ inputs.artifact }}
path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net10.0/openapi.json
retention-days: 14
if-no-files-found: error

View File

@@ -1,80 +0,0 @@
name: OpenAPI Check
on:
pull_request:
jobs:
ancestor:
name: Common Ancestor
runs-on: ubuntu-latest
outputs:
base_ref: ${{ steps.ancestor.outputs.base_ref }}
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
fetch-depth: 0
- name: Search History
id: ancestor
run: |
git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
git fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} HEAD)
echo "ref: ${ANCESTOR_REF}"
echo "base_ref=${ANCESTOR_REF}" >> "$GITHUB_OUTPUT"
head:
name: Head Artifact
uses: ./.github/workflows/openapi-generate.yml
with:
ref: ${{ github.event.pull_request.head.sha }}
repository: ${{ github.event.pull_request.head.repo.full_name }}
artifact: openapi-head
base:
name: Base Artifact
uses: ./.github/workflows/openapi-generate.yml
needs:
- ancestor
with:
ref: ${{ needs.ancestor.outputs.base_ref }}
repository: ${{ github.event.pull_request.base.repo.full_name }}
artifact: openapi-base
diff:
name: Generate Report
runs-on: ubuntu-latest
needs:
- head
- base
steps:
- name: Download Head
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openapi-head
path: openapi-head
- name: Download Base
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openapi-base
path: openapi-base
- name: Detect Changes
id: openapi-diff
run: |
sed -i 's:allOf:oneOf:g' openapi-head/openapi.json
sed -i 's:allOf:oneOf:g' openapi-base/openapi.json
mkdir -p /tmp/openapi-report
mv openapi-head/openapi.json /tmp/openapi-report/head.json
mv openapi-base/openapi.json /tmp/openapi-report/base.json
docker run -v /tmp/openapi-report:/data openapitools/openapi-diff:2.1.6 /data/base.json /data/head.json --state -l ERROR --markdown /data/openapi-report.md
- name: Upload Artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: openapi-report
path: /tmp/openapi-report/openapi-report.md

View File

@@ -1,59 +0,0 @@
name: OpenAPI Report
on:
workflow_run:
workflows:
- OpenAPI Check
types:
- completed
jobs:
metadata:
name: Generate Metadata
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
outputs:
pr_number: ${{ steps.pr_number.outputs.pr_number }}
steps:
- name: Get Pull Request Number
id: pr_number
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
API_RESPONSE=$(gh pr list --repo "${GITHUB_REPOSITORY}" --search "${HEAD_SHA}" --state open --json number)
PR_NUMBER=$(echo "${API_RESPONSE}" | jq '.[0].number')
echo "repository: ${GITHUB_REPOSITORY}"
echo "sha: ${HEAD_SHA}"
echo "response: ${API_RESPONSE}"
echo "pr: ${PR_NUMBER}"
echo "pr_number=${PR_NUMBER}" >> "${GITHUB_OUTPUT}"
comment:
name: Pull Request Comment
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
needs:
- metadata
permissions:
pull-requests: write
actions: read
contents: read
steps:
- name: Download OpenAPI Report
id: download_report
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: openapi-report
path: openapi-report
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Push Comment
uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
with:
github-token: ${{ secrets.JF_BOT_TOKEN }}
file-path: ${{ steps.download_report.outputs.download-path }}/openapi-report.md
pr-number: ${{ needs.metadata.outputs.pr_number }}
comment-tag: openapi-report

View File

@@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- master - master
pull_request: pull_request_target:
issue_comment: issue_comment:
permissions: {} permissions: {}

View File

@@ -4,7 +4,7 @@ on:
push: push:
branches: branches:
- master - master
pull_request: pull_request_target:
issue_comment: issue_comment:
permissions: {} permissions: {}
@@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Apply label - name: Apply label
uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request'}} if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
with: with:
dirtyLabel: 'merge conflict' dirtyLabel: 'merge conflict'
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.' commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.repository, 'jellyfin/') }} if: ${{ contains(github.repository, 'jellyfin/') }}
steps: steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0 - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
ascending: true ascending: true

View File

@@ -28,7 +28,7 @@ jobs:
timeoutSeconds: 3600 timeoutSeconds: 3600
- name: Setup YQ - name: Setup YQ
uses: chrisdickinson/setup-yq@fa3192edd79d6eb0e4e12de8dde3a0c26f2b853b # latest uses: chrisdickinson/setup-yq@latest
with: with:
yq-version: v4.9.8 yq-version: v4.9.8

View File

@@ -1,6 +1,5 @@
# Jellyfin Contributors # Jellyfin Contributors
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [1337joe](https://github.com/1337joe) - [1337joe](https://github.com/1337joe)
- [97carmine](https://github.com/97carmine) - [97carmine](https://github.com/97carmine)
- [Abbe98](https://github.com/Abbe98) - [Abbe98](https://github.com/Abbe98)
@@ -15,7 +14,7 @@
- [bilde2910](https://github.com/bilde2910) - [bilde2910](https://github.com/bilde2910)
- [bfayers](https://github.com/bfayers) - [bfayers](https://github.com/bfayers)
- [BnMcG](https://github.com/BnMcG) - [BnMcG](https://github.com/BnMcG)
- [Bond_009](https://github.com/Bond-009) - [Bond-009](https://github.com/Bond-009)
- [brianjmurrell](https://github.com/brianjmurrell) - [brianjmurrell](https://github.com/brianjmurrell)
- [bugfixin](https://github.com/bugfixin) - [bugfixin](https://github.com/bugfixin)
- [chaosinnovator](https://github.com/chaosinnovator) - [chaosinnovator](https://github.com/chaosinnovator)
@@ -32,7 +31,6 @@
- [DaveChild](https://github.com/DaveChild) - [DaveChild](https://github.com/DaveChild)
- [DavidFair](https://github.com/DavidFair) - [DavidFair](https://github.com/DavidFair)
- [Delgan](https://github.com/Delgan) - [Delgan](https://github.com/Delgan)
- [DerMaddis](https://github.com/dermaddis)
- [Derpipose](https://github.com/Derpipose) - [Derpipose](https://github.com/Derpipose)
- [dcrdev](https://github.com/dcrdev) - [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung) - [dhartung](https://github.com/dhartung)
@@ -56,7 +54,6 @@
- [geilername](https://github.com/geilername) - [geilername](https://github.com/geilername)
- [GermanCoding](https://github.com/GermanCoding) - [GermanCoding](https://github.com/GermanCoding)
- [gnattu](https://github.com/gnattu) - [gnattu](https://github.com/gnattu)
- [gnuyent](https://github.com/gnuyent)
- [GodTamIt](https://github.com/GodTamIt) - [GodTamIt](https://github.com/GodTamIt)
- [grafixeyehero](https://github.com/grafixeyehero) - [grafixeyehero](https://github.com/grafixeyehero)
- [h1nk](https://github.com/h1nk) - [h1nk](https://github.com/h1nk)
@@ -64,7 +61,6 @@
- [HelloWorld017](https://github.com/HelloWorld017) - [HelloWorld017](https://github.com/HelloWorld017)
- [ikomhoog](https://github.com/ikomhoog) - [ikomhoog](https://github.com/ikomhoog)
- [iwalton3](https://github.com/iwalton3) - [iwalton3](https://github.com/iwalton3)
- [Jakob Kukla](https://github.com/jakobkukla)
- [jftuga](https://github.com/jftuga) - [jftuga](https://github.com/jftuga)
- [jkhsjdhjs](https://github.com/jkhsjdhjs) - [jkhsjdhjs](https://github.com/jkhsjdhjs)
- [jmshrv](https://github.com/jmshrv) - [jmshrv](https://github.com/jmshrv)
@@ -73,10 +69,8 @@
- [JustAMan](https://github.com/JustAMan) - [JustAMan](https://github.com/JustAMan)
- [justinfenn](https://github.com/justinfenn) - [justinfenn](https://github.com/justinfenn)
- [JPVenson](https://github.com/JPVenson) - [JPVenson](https://github.com/JPVenson)
- [JPUC1143](https://github.com/Jpuc1143/)
- [KerryRJ](https://github.com/KerryRJ) - [KerryRJ](https://github.com/KerryRJ)
- [Larvitar](https://github.com/Larvitar) - [Larvitar](https://github.com/Larvitar)
- [lbenini](https://github.com/lbenini)
- [LeoVerto](https://github.com/LeoVerto) - [LeoVerto](https://github.com/LeoVerto)
- [Liggy](https://github.com/Liggy) - [Liggy](https://github.com/Liggy)
- [lmaonator](https://github.com/lmaonator) - [lmaonator](https://github.com/lmaonator)
@@ -89,19 +83,15 @@
- [marius-luca-87](https://github.com/marius-luca-87) - [marius-luca-87](https://github.com/marius-luca-87)
- [mark-monteiro](https://github.com/mark-monteiro) - [mark-monteiro](https://github.com/mark-monteiro)
- [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti) - [MarkCiliaVincenti](https://github.com/MarkCiliaVincenti)
- [Martin Reuter](https://github.com/reuterma24)
- [Matt07211](https://github.com/Matt07211) - [Matt07211](https://github.com/Matt07211)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [Maxr1998](https://github.com/Maxr1998) - [Maxr1998](https://github.com/Maxr1998)
- [mcarlton00](https://github.com/mcarlton00) - [mcarlton00](https://github.com/mcarlton00)
- [Michael McElroy](https://github.com/mcmcelro)
- [mitchfizz05](https://github.com/mitchfizz05) - [mitchfizz05](https://github.com/mitchfizz05)
- [mohd-akram](https://github.com/mohd-akram) - [mohd-akram](https://github.com/mohd-akram)
- [MrTimscampi](https://github.com/MrTimscampi) - [MrTimscampi](https://github.com/MrTimscampi)
- [n8225](https://github.com/n8225) - [n8225](https://github.com/n8225)
- [Nalsai](https://github.com/Nalsai) - [Nalsai](https://github.com/Nalsai)
- [Narfinger](https://github.com/Narfinger) - [Narfinger](https://github.com/Narfinger)
- [Nathan McCrina](https://github.com/nfmccrina)
- [NathanPickard](https://github.com/NathanPickard) - [NathanPickard](https://github.com/NathanPickard)
- [neilsb](https://github.com/neilsb) - [neilsb](https://github.com/neilsb)
- [nevado](https://github.com/nevado) - [nevado](https://github.com/nevado)
@@ -112,19 +102,16 @@
- [OancaAndrei](https://github.com/OancaAndrei) - [OancaAndrei](https://github.com/OancaAndrei)
- [obradovichv](https://github.com/obradovichv) - [obradovichv](https://github.com/obradovichv)
- [oddstr13](https://github.com/oddstr13) - [oddstr13](https://github.com/oddstr13)
- [olsh](https://github.com/olsh)
- [orryverducci](https://github.com/orryverducci) - [orryverducci](https://github.com/orryverducci)
- [petermcneil](https://github.com/petermcneil) - [petermcneil](https://github.com/petermcneil)
- [Phlogi](https://github.com/Phlogi) - [Phlogi](https://github.com/Phlogi)
- [pjeanjean](https://github.com/pjeanjean) - [pjeanjean](https://github.com/pjeanjean)
- [ploughpuff](https://github.com/ploughpuff) - [ploughpuff](https://github.com/ploughpuff)
- [poytiis](https://github.com/poytiis)
- [pR0Ps](https://github.com/pR0Ps) - [pR0Ps](https://github.com/pR0Ps)
- [PrplHaz4](https://github.com/PrplHaz4) - [PrplHaz4](https://github.com/PrplHaz4)
- [RazeLighter777](https://github.com/RazeLighter777) - [RazeLighter777](https://github.com/RazeLighter777)
- [redSpoutnik](https://github.com/redSpoutnik) - [redSpoutnik](https://github.com/redSpoutnik)
- [ringmatter](https://github.com/ringmatter) - [ringmatter](https://github.com/ringmatter)
- [Robert Lützner](https://github.com/rluetzner)
- [ryan-hartzell](https://github.com/ryan-hartzell) - [ryan-hartzell](https://github.com/ryan-hartzell)
- [s0urcelab](https://github.com/s0urcelab) - [s0urcelab](https://github.com/s0urcelab)
- [sachk](https://github.com/sachk) - [sachk](https://github.com/sachk)
@@ -140,7 +127,6 @@
- [sl1288](https://github.com/sl1288) - [sl1288](https://github.com/sl1288)
- [Smith00101010](https://github.com/Smith00101010) - [Smith00101010](https://github.com/Smith00101010)
- [sorinyo2004](https://github.com/sorinyo2004) - [sorinyo2004](https://github.com/sorinyo2004)
- [Soumyadip Auddy](https://github.com/SoumyadipAuddy)
- [sparky8251](https://github.com/sparky8251) - [sparky8251](https://github.com/sparky8251)
- [spookbits](https://github.com/spookbits) - [spookbits](https://github.com/spookbits)
- [ssenart](https://github.com/ssenart) - [ssenart](https://github.com/ssenart)
@@ -163,7 +149,6 @@
- [twinkybot](https://github.com/twinkybot) - [twinkybot](https://github.com/twinkybot)
- [Ullmie02](https://github.com/Ullmie02) - [Ullmie02](https://github.com/Ullmie02)
- [Unhelpful](https://github.com/Unhelpful) - [Unhelpful](https://github.com/Unhelpful)
- [Utku Özdemir](https://github.com/utkuozdemir)
- [viaregio](https://github.com/viaregio) - [viaregio](https://github.com/viaregio)
- [vitorsemeano](https://github.com/vitorsemeano) - [vitorsemeano](https://github.com/vitorsemeano)
- [voodoos](https://github.com/voodoos) - [voodoos](https://github.com/voodoos)
@@ -179,7 +164,6 @@
- [XVicarious](https://github.com/XVicarious) - [XVicarious](https://github.com/XVicarious)
- [YouKnowBlom](https://github.com/YouKnowBlom) - [YouKnowBlom](https://github.com/YouKnowBlom)
- [ZachPhelan](https://github.com/ZachPhelan) - [ZachPhelan](https://github.com/ZachPhelan)
- [ZeusCraft10](https://github.com/ZeusCraft10)
- [KristupasSavickas](https://github.com/KristupasSavickas) - [KristupasSavickas](https://github.com/KristupasSavickas)
- [Pusta](https://github.com/pusta) - [Pusta](https://github.com/pusta)
- [nielsvanvelzen](https://github.com/nielsvanvelzen) - [nielsvanvelzen](https://github.com/nielsvanvelzen)
@@ -227,10 +211,6 @@
- [martenumberto](https://github.com/martenumberto) - [martenumberto](https://github.com/martenumberto)
- [ZeusCraft10](https://github.com/ZeusCraft10) - [ZeusCraft10](https://github.com/ZeusCraft10)
- [MarcoCoreDuo](https://github.com/MarcoCoreDuo) - [MarcoCoreDuo](https://github.com/MarcoCoreDuo)
- [LiHRaM](https://github.com/LiHRaM)
- [MSalman5230](https://github.com/MSalman5230)
- [dwandw](https://github.com/dwandw)
- [Lampan-git](https://github.com/Lampan-git)
# Emby Contributors # Emby Contributors
@@ -294,3 +274,16 @@
- [tikuf](https://github.com/tikuf/) - [tikuf](https://github.com/tikuf/)
- [Tim Hobbs](https://github.com/timhobbs) - [Tim Hobbs](https://github.com/timhobbs)
- [SvenVandenbrande](https://github.com/SvenVandenbrande) - [SvenVandenbrande](https://github.com/SvenVandenbrande)
- [olsh](https://github.com/olsh)
- [lbenini](https://github.com/lbenini)
- [gnuyent](https://github.com/gnuyent)
- [Matthew Jones](https://github.com/matthew-jones-uk)
- [Jakob Kukla](https://github.com/jakobkukla)
- [Utku Özdemir](https://github.com/utkuozdemir)
- [JPUC1143](https://github.com/Jpuc1143/)
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
- [Robert Lützner](https://github.com/rluetzner)
- [Nathan McCrina](https://github.com/nfmccrina)
- [Martin Reuter](https://github.com/reuterma24)
- [Michael McElroy](https://github.com/mcmcelro)
- [Soumyadip Auddy](https://github.com/SoumyadipAuddy)

View File

@@ -4,52 +4,52 @@
</PropertyGroup> </PropertyGroup>
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.--> <!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
<ItemGroup Label="Package Dependencies"> <ItemGroup Label="Package Dependencies">
<PackageVersion Include="AsyncKeyedLock" Version="8.0.2" /> <PackageVersion Include="AsyncKeyedLock" Version="8.0.0" />
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" /> <PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.1" />
<PackageVersion Include="AutoFixture.Xunit3" Version="4.19.0" /> <PackageVersion Include="AutoFixture.Xunit2" Version="4.18.1" />
<PackageVersion Include="AutoFixture" Version="4.18.1" /> <PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BDInfo" Version="0.8.0" /> <PackageVersion Include="BDInfo" Version="0.8.0" />
<PackageVersion Include="BitFaster.Caching" Version="2.5.4" /> <PackageVersion Include="BitFaster.Caching" Version="2.5.4" />
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" /> <PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" /> <PackageVersion Include="BlurHashSharp" Version="1.4.0-pre.1" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" /> <PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="coverlet.collector" Version="10.0.0" /> <PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="Diacritics" Version="4.1.8" /> <PackageVersion Include="Diacritics" Version="4.1.4" />
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" /> <PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
<PackageVersion Include="DotNet.Glob" Version="3.1.3" /> <PackageVersion Include="DotNet.Glob" Version="3.1.3" />
<PackageVersion Include="FsCheck.Xunit.v3" Version="3.3.3" /> <PackageVersion Include="FsCheck.Xunit" Version="3.3.2" />
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" /> <PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="8.3.1.1" />
<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.8" /> <PackageVersion Include="IDisposableAnalyzers" Version="4.0.8" />
<PackageVersion Include="Ignore" Version="0.2.1" /> <PackageVersion Include="Ignore" Version="0.2.1" />
<PackageVersion Include="Jellyfin.XmlTv" Version="10.12.0-pre1" /> <PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
<PackageVersion Include="libse" Version="4.0.12" /> <PackageVersion Include="libse" Version="4.0.12" />
<PackageVersion Include="LrcParser" Version="2025.623.0" /> <PackageVersion Include="LrcParser" Version="2025.623.0" />
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="8.0.1" /> <PackageVersion Include="MetaBrainz.MusicBrainz" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.8" /> <PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.8" /> <PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="4.14.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.3.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.Common" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" /> <PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" />
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.8" /> <PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.8" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.8" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.8" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.2" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.8" /> <PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.8" /> <PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.5.1" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageVersion Include="MimeTypes" Version="2.5.2" /> <PackageVersion Include="MimeTypes" Version="2.5.2" />
<PackageVersion Include="Morestachio" Version="5.0.1.670" /> <PackageVersion Include="Morestachio" Version="5.0.1.631" />
<PackageVersion Include="Moq" Version="4.18.4" /> <PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="1.1.0.5" /> <PackageVersion Include="NEbml" Version="1.1.0.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" /> <PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
@@ -57,7 +57,7 @@
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" /> <PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" /> <PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />
<PackageVersion Include="prometheus-net" Version="8.2.1" /> <PackageVersion Include="prometheus-net" Version="8.2.1" />
<PackageVersion Include="Polly" Version="8.6.6" /> <PackageVersion Include="Polly" Version="8.6.5" />
<PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" /> <PackageVersion Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" /> <PackageVersion Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageVersion Include="Serilog.Expressions" Version="5.0.0" /> <PackageVersion Include="Serilog.Expressions" Version="5.0.0" />
@@ -74,16 +74,17 @@
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="[3.116.1]" /> <PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="[3.116.1]" />
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" /> <PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" /> <PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
<PackageVersion Include="Svg.Skia" Version="3.4.1" /> <PackageVersion Include="Svg.Skia" Version="3.2.1" />
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="10.1.7" /> <PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.9.0" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="10.1.7" /> <PackageVersion Include="Swashbuckle.AspNetCore" Version="7.3.2" />
<PackageVersion Include="System.Text.Json" Version="10.0.8" /> <PackageVersion Include="System.Text.Json" Version="10.0.2" />
<PackageVersion Include="TagLibSharp" Version="2.3.0" /> <PackageVersion Include="TagLibSharp" Version="2.3.0" />
<PackageVersion Include="z440.atl.core" Version="7.13.0" /> <PackageVersion Include="z440.atl.core" Version="7.10.0" />
<PackageVersion Include="TMDbLib" Version="3.0.0" /> <PackageVersion Include="TMDbLib" Version="2.3.0" />
<PackageVersion Include="UTF.Unknown" Version="2.6.0" /> <PackageVersion Include="UTF.Unknown" Version="2.6.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" /> <PackageVersion Include="Xunit.Priority" Version="1.1.6" />
<PackageVersion Include="xunit.v3" Version="3.2.2" /> <PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Xunit.v3.Priority" Version="1.1.18" /> <PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
<PackageVersion Include="xunit" Version="2.9.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,3 @@
#pragma warning disable CA1815
namespace Emby.Naming.AudioBook namespace Emby.Naming.AudioBook
{ {
/// <summary> /// <summary>

View File

@@ -152,8 +152,8 @@ namespace Emby.Naming.Common
CleanStrings = CleanStrings =
[ [
@"^\s*(?<cleaned>.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multi|subs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS)(?=[ _\,\.\(\)\[\]\-]|$)", @"^\s*(?<cleaned>.+?)[ _\,\.\(\)\[\]\-](3d|sbs|tab|hsbs|htab|mvc|HDR|HDC|UHD|UltraHD|4k|ac3|dts|custom|dc|divx|divx5|dsr|dsrip|dutch|dvd|dvdrip|dvdscr|dvdscreener|screener|dvdivx|cam|fragment|fs|hdtv|hdrip|hdtvrip|internal|limited|multi|subs|ntsc|ogg|ogm|pal|pdtv|proper|repack|rerip|retail|cd[1-9]|r5|bd5|bd|se|svcd|swedish|german|read.nfo|nfofix|unrated|ws|telesync|ts|telecine|tc|brrip|bdrip|480p|480i|576p|576i|720p|720i|1080p|1080i|2160p|hrhd|hrhdtv|hddvd|bluray|blu-ray|x264|x265|h264|h265|xvid|xvidvd|xxx|www.www|AAC|DTS|\[.*\])([ _\,\.\(\)\[\]\-]|$)",
@"^\s*(?<cleaned>.+?)((\s*\[[^\]]+\]\s*)+)(\.[^\s]+)?$", @"^(?<cleaned>.+?)(\[.*\])",
@"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)", @"^\s*(?<cleaned>.+?)\WE[0-9]+(-|~)E?[0-9]+(\W|$)",
@"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)", @"^\s*\[[^\]]+\](?!\.\w+$)\s*(?<cleaned>.+)",
@"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$", @"^\s*(?<cleaned>.+?)\s+-\s+[0-9]+\s*$",
@@ -225,7 +225,6 @@ namespace Emby.Naming.Common
".afc", ".afc",
".amf", ".amf",
".aif", ".aif",
".aifc",
".aiff", ".aiff",
".alac", ".alac",
".amr", ".amr",
@@ -379,14 +378,6 @@ namespace Emby.Naming.Common
IsNamed = true IsNamed = true
}, },
// "Name - 101.mkv", "Name - 101 [720p].mkv", "Name - 101 (2020).mkv"
// Handles absolute episode numbers with hyphen delimiter (common in anime)
// Without brackets (bracketed version handled above)
new EpisodeExpression(@".*[\\\/](?<seriesname>[^\\\/]+?)[\s_]+-[\s_]+(?<epnumber>[0-9]+)[\s_]*(?:\[.*?\]|\(.*?\))*[\s_]*(?:\.\w+)?$")
{
IsNamed = true
},
// /server/anything_102.mp4 // /server/anything_102.mp4
// /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv
// /server/anything_1996.11.14.mp4 // /server/anything_1996.11.14.mp4

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>12.0.0</VersionPrefix> <VersionPrefix>10.12.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

@@ -12,10 +12,10 @@ namespace Emby.Naming.TV
{ {
private static readonly Regex CleanNameRegex = new(@"[ ._\-\[\]]", RegexOptions.Compiled); private static readonly Regex CleanNameRegex = new(@"[ ._\-\[\]]", RegexOptions.Compiled);
[GeneratedRegex(@"^\s*((?<seasonnumber>(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[]*|[]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul|érie|éria|erie|eria)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<rightpart>.*)$", RegexOptions.IgnoreCase)] [GeneratedRegex(@"^\s*((?<seasonnumber>(?>\d+))(?:st|nd|rd|th|\.)*(?!\s*[Ee]\d+))\s*(?:[[]*|[]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
private static partial Regex ProcessPre(); private static partial Regex ProcessPre();
[GeneratedRegex(@"^\s*(?:[[]*|[]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul|érie|éria|erie|eria)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<seasonnumber>\d+?)(?=\d{3,4}p|[^\d]|$)(?!\s*[Ee]\d)(?<rightpart>.*)$", RegexOptions.IgnoreCase)] [GeneratedRegex(@"^\s*(?:[[]*|[]*|[sS](?:eason|æson|aison|taffel|eries|tagione|äsong|eizoen|easong|ezon|ezona|ezóna|ezonul)*|[tT](?:emporada)*|[kK](?:ausi)*|[Сс](?:езон)*)\s*(?<seasonnumber>\d+?)(?=\d{3,4}p|[^\d]|$)(?!\s*[Ee]\d)(?<rightpart>.*)$", RegexOptions.IgnoreCase)]
private static partial Regex ProcessPost(); private static partial Regex ProcessPost();
[GeneratedRegex(@"[sS](\d{1,4})(?!\d|[eE]\d)(?=\.|_|-|\[|\]|\s|$)", RegexOptions.None)] [GeneratedRegex(@"[sS](\d{1,4})(?!\d|[eE]\d)(?=\.|_|-|\[|\]|\s|$)", RegexOptions.None)]

View File

@@ -18,7 +18,7 @@ public static class TvParserHelpers
/// <param name="status">The status string.</param> /// <param name="status">The status string.</param>
/// <param name="enumValue">The <see cref="SeriesStatus"/>.</param> /// <param name="enumValue">The <see cref="SeriesStatus"/>.</param>
/// <returns>Returns true if parsing was successful.</returns> /// <returns>Returns true if parsing was successful.</returns>
public static bool TryParseSeriesStatus(string? status, out SeriesStatus? enumValue) public static bool TryParseSeriesStatus(string status, out SeriesStatus? enumValue)
{ {
if (Enum.TryParse(status, true, out SeriesStatus seriesStatus)) if (Enum.TryParse(status, true, out SeriesStatus seriesStatus))
{ {

View File

@@ -1,5 +1,3 @@
#pragma warning disable CA1815
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
/// <summary> /// <summary>

View File

@@ -44,7 +44,7 @@ namespace Emby.Naming.Video
var match = expression.Match(name); var match = expression.Match(name);
if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned)) if (match.Success && match.Groups.TryGetValue("cleaned", out var cleaned))
{ {
newName = cleaned.Value.Trim(); newName = cleaned.Value;
return true; return true;
} }

View File

@@ -17,8 +17,8 @@ namespace Emby.Naming.Video
{ {
Name = name; Name = name;
Files = []; Files = Array.Empty<VideoFileInfo>();
AlternateVersions = []; AlternateVersions = Array.Empty<VideoFileInfo>();
} }
/// <summary> /// <summary>
@@ -40,10 +40,10 @@ namespace Emby.Naming.Video
public IReadOnlyList<VideoFileInfo> Files { get; set; } public IReadOnlyList<VideoFileInfo> Files { get; set; }
/// <summary> /// <summary>
/// Gets or sets the alternate versions. Each alternate may itself span multiple files. /// Gets or sets the alternate versions.
/// </summary> /// </summary>
/// <value>The alternate versions.</value> /// <value>The alternate versions.</value>
public IReadOnlyList<VideoInfo> AlternateVersions { get; set; } public IReadOnlyList<VideoFileInfo> AlternateVersions { get; set; }
/// <summary> /// <summary>
/// Gets or sets the extra type. /// Gets or sets the extra type.

View File

@@ -5,8 +5,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.TV; using Jellyfin.Extensions;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
namespace Emby.Naming.Video namespace Emby.Naming.Video
@@ -14,23 +13,8 @@ namespace Emby.Naming.Video
/// <summary> /// <summary>
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
/// </summary> /// </summary>
public partial class VideoListResolver public static partial class VideoListResolver
{ {
private static readonly StringComparer _numericOrdinalComparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering);
private readonly NamingOptions _namingOptions;
private readonly EpisodePathParser _episodePathParser;
/// <summary>
/// Initializes a new instance of the <see cref="VideoListResolver"/> class.
/// </summary>
/// <param name="namingOptions">The naming options.</param>
public VideoListResolver(NamingOptions namingOptions)
{
_namingOptions = namingOptions;
_episodePathParser = new EpisodePathParser(namingOptions);
}
[GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)] [GeneratedRegex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase)]
private static partial Regex ResolutionRegex(); private static partial Regex ResolutionRegex();
@@ -41,12 +25,12 @@ namespace Emby.Naming.Video
/// Resolves alternative versions and extras from list of video files. /// Resolves alternative versions and extras from list of video files.
/// </summary> /// </summary>
/// <param name="videoInfos">List of related video files.</param> /// <param name="videoInfos">List of related video files.</param>
/// <param name="namingOptions">The naming options.</param>
/// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param> /// <param name="supportMultiVersion">Indication we should consider multi-versions of content.</param>
/// <param name="parseName">Whether to parse the name or use the filename.</param> /// <param name="parseName">Whether to parse the name or use the filename.</param>
/// <param name="libraryRoot">Top-level folder for the containing library.</param> /// <param name="libraryRoot">Top-level folder for the containing library.</param>
/// <param name="collectionType">The type of the containing collection, if known.</param>
/// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns> /// <returns>Returns enumerable of <see cref="VideoInfo"/> which groups files together when related.</returns>
public IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, bool supportMultiVersion = true, bool parseName = true, string? libraryRoot = "", CollectionType? collectionType = null) public static IReadOnlyList<VideoInfo> Resolve(IReadOnlyList<VideoFileInfo> videoInfos, NamingOptions namingOptions, bool supportMultiVersion = true, bool parseName = true, string? libraryRoot = "")
{ {
// Filter out all extras, otherwise they could cause stacks to not be resolved // Filter out all extras, otherwise they could cause stacks to not be resolved
// See the unit test TestStackedWithTrailer // See the unit test TestStackedWithTrailer
@@ -54,7 +38,7 @@ namespace Emby.Naming.Video
.Where(i => i.ExtraType is null) .Where(i => i.ExtraType is null)
.Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory }); .Select(i => new FileSystemMetadata { FullName = i.Path, IsDirectory = i.IsDirectory });
var stackResult = StackResolver.Resolve(nonExtras, _namingOptions).ToList(); var stackResult = StackResolver.Resolve(nonExtras, namingOptions).ToList();
var remainingFiles = new List<VideoFileInfo>(); var remainingFiles = new List<VideoFileInfo>();
var standaloneMedia = new List<VideoFileInfo>(); var standaloneMedia = new List<VideoFileInfo>();
@@ -83,7 +67,7 @@ namespace Emby.Naming.Video
{ {
var info = new VideoInfo(stack.Name) var info = new VideoInfo(stack.Name)
{ {
Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, _namingOptions, parseName, libraryRoot)) Files = stack.Files.Select(i => VideoResolver.Resolve(i, stack.IsDirectoryStack, namingOptions, parseName, libraryRoot))
.OfType<VideoFileInfo>() .OfType<VideoFileInfo>()
.ToList() .ToList()
}; };
@@ -102,9 +86,7 @@ namespace Emby.Naming.Video
if (supportMultiVersion) if (supportMultiVersion)
{ {
list = collectionType is CollectionType.tvshows list = GetVideosGroupedByVersion(list, namingOptions);
? GetEpisodesGroupedByVersion(list)
: GetVideosGroupedByVersion(list);
} }
// Whatever files are left, just add them // Whatever files are left, just add them
@@ -118,7 +100,7 @@ namespace Emby.Naming.Video
return list; return list;
} }
private List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos) private static List<VideoInfo> GetVideosGroupedByVersion(List<VideoInfo> videos, NamingOptions namingOptions)
{ {
if (videos.Count == 0) if (videos.Count == 0)
{ {
@@ -142,7 +124,7 @@ namespace Emby.Naming.Video
continue; continue;
} }
if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension)) if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions))
{ {
return videos; return videos;
} }
@@ -153,9 +135,45 @@ namespace Emby.Naming.Video
} }
} }
var organized = OrganizeAlternateVersions(videos, primary, folderName.ToString()); if (videos.Count > 1)
{
var groups = videos
.Select(x => (filename: x.Files[0].FileNameWithoutExtension.ToString(), value: x))
.Select(x => (x.filename, resolutionMatch: ResolutionRegex().Match(x.filename), x.value))
.GroupBy(x => x.resolutionMatch.Success)
.ToList();
return [organized]; videos.Clear();
StringComparer comparer = StringComparer.Create(CultureInfo.InvariantCulture, CompareOptions.NumericOrdering);
foreach (var group in groups)
{
if (group.Key)
{
videos.InsertRange(0, group
.OrderByDescending(x => x.resolutionMatch.Value, comparer)
.ThenBy(x => x.filename, comparer)
.Select(x => x.value));
}
else
{
videos.AddRange(group.OrderBy(x => x.filename, comparer).Select(x => x.value));
}
}
}
primary ??= videos[0];
videos.Remove(primary);
var list = new List<VideoInfo>
{
primary
};
list[0].AlternateVersions = videos.Select(x => x.Files[0]).ToArray();
list[0].Name = folderName.ToString();
return list;
} }
private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos) private static bool HaveSameYear(IReadOnlyList<VideoInfo> videos)
@@ -177,7 +195,7 @@ namespace Emby.Naming.Video
return true; return true;
} }
private bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename) private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename, NamingOptions namingOptions)
{ {
if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
@@ -191,7 +209,7 @@ namespace Emby.Naming.Video
} }
// There are no span overloads for regex unfortunately // There are no span overloads for regex unfortunately
if (CleanStringParser.TryClean(testFilename.ToString(), _namingOptions.CleanStringRegexes, out var cleanName)) if (CleanStringParser.TryClean(testFilename.ToString(), namingOptions.CleanStringRegexes, out var cleanName))
{ {
testFilename = cleanName.AsSpan().Trim(); testFilename = cleanName.AsSpan().Trim();
} }
@@ -199,117 +217,7 @@ namespace Emby.Naming.Video
// The CleanStringParser should have removed common keywords etc. // The CleanStringParser should have removed common keywords etc.
return testFilename.IsEmpty return testFilename.IsEmpty
|| testFilename[0] == '-' || testFilename[0] == '-'
|| testFilename[0] == '_'
|| testFilename[0] == '.'
|| CheckMultiVersionRegex().IsMatch(testFilename); || CheckMultiVersionRegex().IsMatch(testFilename);
} }
private List<VideoInfo> GetEpisodesGroupedByVersion(List<VideoInfo> videos)
{
if (videos.Count < 2)
{
return videos;
}
var result = new List<VideoInfo>();
var groups = new Dictionary<string, List<VideoInfo>>(StringComparer.OrdinalIgnoreCase);
for (var i = 0; i < videos.Count; i++)
{
var video = videos[i];
var episodeResult = _episodePathParser.Parse(video.Files[0].Path, false);
string? key = null;
if (episodeResult.Success)
{
if (episodeResult.IsByDate
&& episodeResult.Year.HasValue
&& episodeResult.Month.HasValue
&& episodeResult.Day.HasValue)
{
key = FormattableString.Invariant(
$"D{episodeResult.Year.Value}{episodeResult.Month.Value:D2}{episodeResult.Day.Value:D2}");
}
else if (episodeResult.EpisodeNumber.HasValue)
{
key = FormattableString.Invariant(
$"S{episodeResult.SeasonNumber ?? 0}E{episodeResult.EpisodeNumber.Value}");
}
}
if (key is null)
{
result.Add(video);
continue;
}
if (!groups.TryGetValue(key, out var group))
{
group = [];
groups[key] = group;
}
group.Add(video);
}
foreach (var group in groups.Values)
{
if (group.Count == 1)
{
result.Add(group[0]);
continue;
}
result.Add(OrganizeAlternateVersions(group));
}
return result;
}
private static VideoInfo OrganizeAlternateVersions(
List<VideoInfo> videos,
VideoInfo? primaryOverride = null,
string? nameOverride = null)
{
if (videos.Count > 1)
{
var groups = videos
.Select(x => (filename: x.Files[0].FileNameWithoutExtension.ToString(), value: x))
.Select(x => (x.filename, resolutionMatch: ResolutionRegex().Match(x.filename), x.value))
.GroupBy(x => x.resolutionMatch.Success)
.ToList();
videos = [];
foreach (var group in groups)
{
if (group.Key)
{
videos.InsertRange(0, group
.OrderByDescending(x => x.resolutionMatch.Value, _numericOrdinalComparer)
.ThenBy(x => x.filename, _numericOrdinalComparer)
.Select(x => x.value));
}
else
{
videos.AddRange(group.OrderBy(x => x.filename, _numericOrdinalComparer).Select(x => x.value));
}
}
}
// Prefer a stacked entry (more than one part) as primary
var primary = primaryOverride
?? videos.FirstOrDefault(v => v.Files.Count > 1)
?? videos[0];
videos.Remove(primary);
primary.AlternateVersions = videos;
if (nameOverride is not null)
{
primary.Name = nameOverride;
}
return primary;
}
} }
} }

View File

@@ -90,7 +90,6 @@ namespace Emby.Server.Implementations.AppBase
CreateAndCheckMarker(ProgramDataPath, "data"); CreateAndCheckMarker(ProgramDataPath, "data");
CreateAndCheckMarker(CachePath, "cache"); CreateAndCheckMarker(CachePath, "cache");
CreateAndCheckMarker(DataPath, "data"); CreateAndCheckMarker(DataPath, "data");
CreateCacheDirTag(CachePath);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -101,26 +100,6 @@ namespace Emby.Server.Implementations.AppBase
CheckOrCreateMarker(path, $".jellyfin-{markerName}", recursive); CheckOrCreateMarker(path, $".jellyfin-{markerName}", recursive);
} }
/// <summary>
/// Creates a CACHEDIR.TAG file in the specified directory per the Cache Directory Tagging specification.
/// This signals to backup tools (e.g. Restic, Borg) that the directory contains cached data
/// and can be excluded from backups.
/// </summary>
/// <param name="path">The cache directory path.</param>
internal static void CreateCacheDirTag(string path)
{
var tagPath = Path.Combine(path, "CACHEDIR.TAG");
if (!File.Exists(tagPath))
{
File.WriteAllText(
tagPath,
"Signature: 8a477f597d28d172789f06886806bc55\n"
+ "# This file is a cache directory tag created by Jellyfin.\n"
+ "# For information about cache directory tags, see:\n"
+ "#\thttps://bford.info/cachedir/\n");
}
}
private IEnumerable<string> GetMarkers(string path, bool recursive = false) private IEnumerable<string> GetMarkers(string path, bool recursive = false)
{ {
return Directory.EnumerateFiles(path, ".jellyfin-*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); return Directory.EnumerateFiles(path, ".jellyfin-*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

View File

@@ -228,7 +228,6 @@ namespace Emby.Server.Implementations.AppBase
Logger.LogInformation("Setting cache path: {Path}", cachePath); Logger.LogInformation("Setting cache path: {Path}", cachePath);
((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath; ((BaseApplicationPaths)CommonApplicationPaths).CachePath = cachePath;
CommonApplicationPaths.CreateAndCheckMarker(((BaseApplicationPaths)CommonApplicationPaths).CachePath, "cache"); CommonApplicationPaths.CreateAndCheckMarker(((BaseApplicationPaths)CommonApplicationPaths).CachePath, "cache");
BaseApplicationPaths.CreateCacheDirTag(cachePath);
} }
/// <summary> /// <summary>

View File

@@ -14,7 +14,6 @@ using System.Reflection;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.Video;
using Emby.Photos; using Emby.Photos;
using Emby.Server.Implementations.Chapters; using Emby.Server.Implementations.Chapters;
using Emby.Server.Implementations.Collections; using Emby.Server.Implementations.Collections;
@@ -26,7 +25,6 @@ using Emby.Server.Implementations.Dto;
using Emby.Server.Implementations.HttpServer.Security; using Emby.Server.Implementations.HttpServer.Security;
using Emby.Server.Implementations.IO; using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library; using Emby.Server.Implementations.Library;
using Emby.Server.Implementations.Library.SimilarItems;
using Emby.Server.Implementations.Localization; using Emby.Server.Implementations.Localization;
using Emby.Server.Implementations.Playlists; using Emby.Server.Implementations.Playlists;
using Emby.Server.Implementations.Plugins; using Emby.Server.Implementations.Plugins;
@@ -94,11 +92,7 @@ using MediaBrowser.Model.System;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using MediaBrowser.Providers.Lyric; using MediaBrowser.Providers.Lyric;
using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Manager;
using MediaBrowser.Providers.Plugins.ListenBrainz;
using MediaBrowser.Providers.Plugins.ListenBrainz.Api;
using MediaBrowser.Providers.Plugins.Tmdb; using MediaBrowser.Providers.Plugins.Tmdb;
using MediaBrowser.Providers.Plugins.Tmdb.Movies;
using MediaBrowser.Providers.Plugins.Tmdb.TV;
using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.Subtitles;
using MediaBrowser.XbmcMetadata.Providers; using MediaBrowser.XbmcMetadata.Providers;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@@ -172,6 +166,8 @@ namespace Emby.Server.Implementations
ConfigurationManager.Configuration, ConfigurationManager.Configuration,
ApplicationPaths.PluginsPath, ApplicationPaths.PluginsPath,
ApplicationVersion); ApplicationVersion);
_disposableParts.Add(_pluginManager);
} }
/// <summary> /// <summary>
@@ -489,11 +485,6 @@ namespace Emby.Server.Implementations
serviceCollection.AddScoped<ISystemManager, SystemManager>(); serviceCollection.AddScoped<ISystemManager, SystemManager>();
serviceCollection.AddSingleton<TmdbClientManager>(); serviceCollection.AddSingleton<TmdbClientManager>();
serviceCollection.AddSingleton<TmdbMovieSimilarProvider>();
serviceCollection.AddSingleton<TmdbSeriesSimilarProvider>();
serviceCollection.AddSingleton<ListenBrainzLabsClient>();
serviceCollection.AddSingleton<ListenBrainzSimilarArtistProvider>();
serviceCollection.AddSingleton(NetManager); serviceCollection.AddSingleton(NetManager);
@@ -516,13 +507,7 @@ namespace Emby.Server.Implementations
serviceCollection.AddSingleton<IUserDataManager, UserDataManager>(); serviceCollection.AddSingleton<IUserDataManager, UserDataManager>();
serviceCollection.AddSingleton<BaseItemRepository>(); serviceCollection.AddSingleton<IItemRepository, BaseItemRepository>();
serviceCollection.AddSingleton<IItemRepository>(sp => sp.GetRequiredService<BaseItemRepository>());
serviceCollection.AddSingleton<IItemQueryHelpers>(sp => sp.GetRequiredService<BaseItemRepository>());
serviceCollection.AddSingleton<IItemPersistenceService, ItemPersistenceService>();
serviceCollection.AddSingleton<INextUpService, NextUpService>();
serviceCollection.AddSingleton<IItemCountService, ItemCountService>();
serviceCollection.AddSingleton<ILinkedChildrenService, LinkedChildrenService>();
serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>(); serviceCollection.AddSingleton<IPeopleRepository, PeopleRepository>();
serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>(); serviceCollection.AddSingleton<IChapterRepository, ChapterRepository>();
serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>(); serviceCollection.AddSingleton<IMediaAttachmentRepository, MediaAttachmentRepository>();
@@ -541,14 +526,10 @@ namespace Emby.Server.Implementations
serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>)); serviceCollection.AddTransient(provider => new Lazy<IUserViewManager>(provider.GetRequiredService<IUserViewManager>));
serviceCollection.AddSingleton<ILibraryManager, LibraryManager>(); serviceCollection.AddSingleton<ILibraryManager, LibraryManager>();
serviceCollection.AddSingleton<NamingOptions>(); serviceCollection.AddSingleton<NamingOptions>();
serviceCollection.AddSingleton<VideoListResolver>();
serviceCollection.AddSingleton<IMusicManager, MusicManager>(); serviceCollection.AddSingleton<IMusicManager, MusicManager>();
serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>(); serviceCollection.AddSingleton<ILibraryMonitor, LibraryMonitor>();
serviceCollection.AddSingleton<DotIgnoreIgnoreRule>();
serviceCollection.AddSingleton<ISimilarItemsManager, SimilarItemsManager>();
serviceCollection.AddSingleton<ISearchEngine, SearchEngine>(); serviceCollection.AddSingleton<ISearchEngine, SearchEngine>();
@@ -660,7 +641,6 @@ namespace Emby.Server.Implementations
BaseItem.ConfigurationManager = ConfigurationManager; BaseItem.ConfigurationManager = ConfigurationManager;
BaseItem.FileSystem = Resolve<IFileSystem>(); BaseItem.FileSystem = Resolve<IFileSystem>();
BaseItem.ItemRepository = Resolve<IItemRepository>(); BaseItem.ItemRepository = Resolve<IItemRepository>();
BaseItem.ItemCountService = Resolve<IItemCountService>();
BaseItem.LibraryManager = Resolve<ILibraryManager>(); BaseItem.LibraryManager = Resolve<ILibraryManager>();
BaseItem.LocalizationManager = Resolve<ILocalizationManager>(); BaseItem.LocalizationManager = Resolve<ILocalizationManager>();
BaseItem.Logger = Resolve<ILogger<BaseItem>>(); BaseItem.Logger = Resolve<ILogger<BaseItem>>();
@@ -707,8 +687,6 @@ namespace Emby.Server.Implementations
GetExports<IExternalUrlProvider>()); GetExports<IExternalUrlProvider>());
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>()); Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
Resolve<ISimilarItemsManager>().AddParts(GetExports<ISimilarItemsProvider>());
} }
/// <summary> /// <summary>
@@ -1028,8 +1006,6 @@ namespace Emby.Server.Implementations
} }
_disposableParts.Clear(); _disposableParts.Clear();
_pluginManager?.Dispose();
} }
_disposed = true; _disposed = true;

View File

@@ -7,7 +7,6 @@ using System.Threading.Tasks;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Chapters;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@@ -129,7 +128,7 @@ public class ChapterManager : IChapterManager
var averageChapterDuration = GetAverageDurationBetweenChapters(chapters); var averageChapterDuration = GetAverageDurationBetweenChapters(chapters);
var threshold = TimeSpan.FromSeconds(1).Ticks; var threshold = TimeSpan.FromSeconds(1).Ticks;
if (chapters.Count >= 2 && averageChapterDuration < threshold) if (averageChapterDuration < threshold)
{ {
_logger.LogInformation("Skipping chapter image extraction for {Video} as the average chapter duration {AverageDuration} was lower than the minimum threshold {Threshold}", video.Name, averageChapterDuration, threshold); _logger.LogInformation("Skipping chapter image extraction for {Video} as the average chapter duration {AverageDuration} was lower than the minimum threshold {Threshold}", video.Name, averageChapterDuration, threshold);
extractImages = false; extractImages = false;
@@ -233,22 +232,12 @@ public class ChapterManager : IChapterManager
} }
/// <inheritdoc /> /// <inheritdoc />
public bool Supports(BaseItem item) public void SaveChapters(Video video, IReadOnlyList<ChapterInfo> chapters)
=> item is Video or Audio;
/// <inheritdoc />
public void SaveChapters(BaseItem item, IReadOnlyList<ChapterInfo> chapters)
{ {
if (!Supports(item)) // Remove any chapters that are outside of the runtime of the video
{ var validChapters = chapters.Where(c => c.StartPositionTicks < video.RunTimeTicks).ToList();
_logger.LogWarning("Attempted to save chapters for unsupported item type {Type}: {Name} ({Id})", item.GetType().Name, item.Name, item.Id); _chapterRepository.SaveChapters(video.Id, validChapters);
return; }
}
// Remove any chapters that are outside of the runtime of the item
var validChapters = chapters.Where(c => c.StartPositionTicks < item.RunTimeTicks).ToList();
_chapterRepository.SaveChapters(item.Id, validChapters);
}
/// <inheritdoc /> /// <inheritdoc />
public ChapterInfo? GetChapter(Guid baseItemId, int index) public ChapterInfo? GetChapter(Guid baseItemId, int index)

View File

@@ -272,7 +272,7 @@ namespace Emby.Server.Implementations.Collections
{ {
var childItem = _libraryManager.GetItemById(guidId); var childItem = _libraryManager.GetItemById(guidId);
var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value.Equals(guidId)); var child = collection.LinkedChildren.FirstOrDefault(i => (i.ItemId.HasValue && i.ItemId.Value.Equals(guidId)) || (childItem is not null && string.Equals(childItem.Path, i.Path, StringComparison.OrdinalIgnoreCase)));
if (child is null) if (child is null)
{ {
@@ -342,7 +342,7 @@ namespace Emby.Server.Implementations.Collections
// this is kind of a performance hack because only Video has alternate versions that should be in a box set? // this is kind of a performance hack because only Video has alternate versions that should be in a box set?
if (item is Video video) if (item is Video video)
{ {
foreach (var childId in _libraryManager.GetLocalAlternateVersionIds(video)) foreach (var childId in video.GetLocalAlternateVersionIds())
{ {
if (!results.ContainsKey(childId)) if (!results.ContainsKey(childId))
{ {

View File

@@ -5,12 +5,10 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations; using Jellyfin.Database.Implementations;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -37,11 +35,7 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var deadItemsProgress = new Progress<double>(val => progress.Report(val * 0.8)); await CleanDeadItems(cancellationToken, progress).ConfigureAwait(false);
await CleanDeadItems(cancellationToken, deadItemsProgress).ConfigureAwait(false);
var playlistProgress = new Progress<double>(val => progress.Report(80 + (val * 0.2)));
await CleanOrphanedFilePlaylistsAsync(cancellationToken, playlistProgress).ConfigureAwait(false);
} }
private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress) private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress)
@@ -122,32 +116,4 @@ public class CleanDatabaseScheduledTask : ILibraryPostScanTask
progress.Report(100); progress.Report(100);
} }
private async Task CleanOrphanedFilePlaylistsAsync(CancellationToken cancellationToken, IProgress<double> progress)
{
var playlists = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.Playlist],
Recursive = true
}).OfType<Playlist>().ToList();
var numComplete = 0;
var numItems = Math.Max(playlists.Count, 1);
foreach (var playlist in playlists)
{
cancellationToken.ThrowIfCancellationRequested();
if (playlist.IsFile && !File.Exists(playlist.Path))
{
_logger.LogInformation("Removing file-based playlist {Name} because source file {Path} no longer exists", playlist.Name, playlist.Path);
_libraryManager.DeleteItem(playlist, new DeleteOptions { DeleteFileLocation = false });
}
numComplete++;
progress.Report((double)numComplete / numItems * 100);
}
progress.Report(100);
}
} }

View File

@@ -153,102 +153,17 @@ namespace Emby.Server.Implementations.Dto
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value; private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<BaseItemDto> GetBaseItemDtos( public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user = null, BaseItem? owner = null)
IReadOnlyList<BaseItem> items,
DtoOptions options,
User? user = null,
BaseItem? owner = null,
bool skipVisibilityCheck = false)
{ {
var accessibleItems = skipVisibilityCheck || user is null ? items : items.Where(x => x.IsVisible(user)).ToList(); var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
var returnItems = new BaseItemDto[accessibleItems.Count]; var returnItems = new BaseItemDto[accessibleItems.Count];
List<(BaseItem, BaseItemDto)>? programTuples = null; List<(BaseItem, BaseItemDto)>? programTuples = null;
List<(BaseItemDto, LiveTvChannel)>? channelTuples = null; List<(BaseItemDto, LiveTvChannel)>? channelTuples = null;
// Batch-fetch user data for all items
Dictionary<Guid, UserItemData>? userDataBatch = null;
if (user is not null && options.EnableUserData)
{
userDataBatch = _userDataRepository.GetUserDataBatch(accessibleItems, user);
}
// Pre-compute collection folders once to avoid N+1 queries in CanDelete
List<Folder>? allCollectionFolders = null;
if (user is not null && options.ContainsField(ItemFields.CanDelete))
{
allCollectionFolders = _libraryManager.GetUserRootFolder().Children.OfType<Folder>().ToList();
}
// Batch-fetch child counts for all folders to avoid N+1 queries
Dictionary<Guid, int>? childCountBatch = null;
if (options.ContainsField(ItemFields.ChildCount))
{
var folderIds = accessibleItems.OfType<Folder>().Select(f => f.Id).ToList();
if (folderIds.Count > 0)
{
childCountBatch = _libraryManager.GetChildCountBatch(folderIds, user?.Id);
}
}
// Batch-fetch played/total counts for all folders to avoid N+1 queries
Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null;
if (user is not null && options.EnableUserData)
{
var folderIds = accessibleItems.OfType<Folder>()
.Where(f => f.SupportsUserDataFromChildren && (f.SupportsPlayedStatus || options.ContainsField(ItemFields.RecursiveItemCount)))
.Select(f => f.Id).ToList();
if (folderIds.Count > 0)
{
playedCountBatch = _libraryManager.GetPlayedAndTotalCountBatch(folderIds, user);
}
}
// Batch-fetch MusicArtist lookups across all items to avoid N+1 queries.
IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null;
var artistNames = new HashSet<string>(StringComparer.Ordinal);
foreach (var item in accessibleItems)
{
if (item is IHasArtist hasArtist)
{
foreach (var name in hasArtist.Artists)
{
if (!string.IsNullOrWhiteSpace(name))
{
artistNames.Add(name);
}
}
}
if (item is IHasAlbumArtist hasAlbumArtist)
{
foreach (var name in hasAlbumArtist.AlbumArtists)
{
if (!string.IsNullOrWhiteSpace(name))
{
artistNames.Add(name);
}
}
}
}
if (artistNames.Count > 0)
{
artistsBatch = _libraryManager.GetArtists(artistNames.ToArray());
}
for (int index = 0; index < accessibleItems.Count; index++) for (int index = 0; index < accessibleItems.Count; index++)
{ {
var item = accessibleItems[index]; var item = accessibleItems[index];
var dto = GetBaseItemDtoInternal( var dto = GetBaseItemDtoInternal(item, options, user, owner);
item,
options,
user,
owner,
userDataBatch?.GetValueOrDefault(item.Id),
allCollectionFolders,
childCountBatch,
playedCountBatch,
artistsBatch);
if (item is LiveTvChannel tvChannel) if (item is LiveTvChannel tvChannel)
{ {
@@ -282,7 +197,7 @@ namespace Emby.Server.Implementations.Dto
public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null) public BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
{ {
var dto = GetBaseItemDtoInternal(item, options, user, owner, null); var dto = GetBaseItemDtoInternal(item, options, user, owner);
if (item is LiveTvChannel tvChannel) if (item is LiveTvChannel tvChannel)
{ {
LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user); LivetvManager.AddChannelInfo(new[] { (dto, tvChannel) }, options, user);
@@ -300,16 +215,7 @@ namespace Emby.Server.Implementations.Dto
return dto; return dto;
} }
private BaseItemDto GetBaseItemDtoInternal( private BaseItemDto GetBaseItemDtoInternal(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null)
BaseItem item,
DtoOptions options,
User? user = null,
BaseItem? owner = null,
UserItemData? userData = null,
List<Folder>? allCollectionFolders = null,
Dictionary<Guid, int>? childCountBatch = null,
Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null,
IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null)
{ {
var dto = new BaseItemDto var dto = new BaseItemDto
{ {
@@ -346,14 +252,7 @@ namespace Emby.Server.Implementations.Dto
if (user is not null) if (user is not null)
{ {
AttachUserSpecificInfo( AttachUserSpecificInfo(dto, item, user, options);
dto,
item,
user,
options,
userData,
childCountBatch,
playedCountBatch);
} }
if (item is IHasMediaSources if (item is IHasMediaSources
@@ -369,15 +268,13 @@ namespace Emby.Server.Implementations.Dto
AttachStudios(dto, item); AttachStudios(dto, item);
} }
AttachBasicFields(dto, item, owner, options, artistsBatch); AttachBasicFields(dto, item, owner, options);
if (options.ContainsField(ItemFields.CanDelete)) if (options.ContainsField(ItemFields.CanDelete))
{ {
dto.CanDelete = user is null dto.CanDelete = user is null
? item.CanDelete() ? item.CanDelete()
: allCollectionFolders is not null : item.CanDelete(user);
? item.CanDelete(user, allCollectionFolders)
: item.CanDelete(user);
} }
if (options.ContainsField(ItemFields.CanDownload)) if (options.ContainsField(ItemFields.CanDownload))
@@ -481,7 +378,37 @@ namespace Emby.Server.Implementations.Dto
return; return;
} }
var counts = _libraryManager.GetItemCountsForNameItem(dto.Type, dto.Id, relatedItemKinds, user); var query = new InternalItemsQuery(user)
{
Recursive = true,
DtoOptions = new DtoOptions(false) { EnableImages = false },
IncludeItemTypes = relatedItemKinds
};
switch (dto.Type)
{
case BaseItemKind.Genre:
case BaseItemKind.MusicGenre:
query.GenreIds = [dto.Id];
break;
case BaseItemKind.MusicArtist:
query.ArtistIds = [dto.Id];
break;
case BaseItemKind.Person:
query.PersonIds = [dto.Id];
break;
case BaseItemKind.Studio:
query.StudioIds = [dto.Id];
break;
case BaseItemKind.Year
when int.TryParse(dto.Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year):
query.Years = [year];
break;
default:
return;
}
var counts = _libraryManager.GetItemCounts(query);
dto.AlbumCount = counts.AlbumCount; dto.AlbumCount = counts.AlbumCount;
dto.ArtistCount = counts.ArtistCount; dto.ArtistCount = counts.ArtistCount;
@@ -531,14 +458,7 @@ namespace Emby.Server.Implementations.Dto
/// <summary> /// <summary>
/// Attaches the user specific info. /// Attaches the user specific info.
/// </summary> /// </summary>
private void AttachUserSpecificInfo( private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, DtoOptions options)
BaseItemDto dto,
BaseItem item,
User user,
DtoOptions options,
UserItemData? userData = null,
Dictionary<Guid, int>? childCountBatch = null,
Dictionary<Guid, (int Played, int Total)>? playedCountBatch = null)
{ {
if (item.IsFolder) if (item.IsFolder)
{ {
@@ -546,19 +466,7 @@ namespace Emby.Server.Implementations.Dto
if (options.EnableUserData) if (options.EnableUserData)
{ {
if (userData is not null) dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
{
// Use pre-fetched user data
dto.UserData = GetUserItemDataDto(userData, item.Id);
(int Played, int Total)? precomputed = playedCountBatch is not null
&& playedCountBatch.TryGetValue(item.Id, out var counts) ? counts : null;
item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options, precomputed);
}
else
{
// Fall back to individual fetch
dto.UserData = _userDataRepository.GetUserDataDto(item, dto, user, options);
}
} }
if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library) if (!dto.ChildCount.HasValue && item.SourceType == SourceType.Library)
@@ -577,7 +485,7 @@ namespace Emby.Server.Implementations.Dto
if (options.ContainsField(ItemFields.ChildCount)) if (options.ContainsField(ItemFields.ChildCount))
{ {
dto.ChildCount ??= GetChildCount(folder, user, childCountBatch); dto.ChildCount ??= GetChildCount(folder, user);
} }
} }
@@ -595,17 +503,7 @@ namespace Emby.Server.Implementations.Dto
{ {
if (options.EnableUserData) if (options.EnableUserData)
{ {
if (userData is not null) dto.UserData = _userDataRepository.GetUserDataDto(item, user);
{
// Use pre-fetched user data
dto.UserData = GetUserItemDataDto(userData, item.Id);
item.FillUserDataDtoValues(dto.UserData, userData, dto, user, options);
}
else
{
// Fall back to individual fetch
dto.UserData = _userDataRepository.GetUserDataDto(item, user);
}
} }
} }
@@ -615,25 +513,7 @@ namespace Emby.Server.Implementations.Dto
} }
} }
private static UserItemDataDto GetUserItemDataDto(UserItemData data, Guid itemId) private static int GetChildCount(Folder folder, User user)
{
ArgumentNullException.ThrowIfNull(data);
return new UserItemDataDto
{
IsFavorite = data.IsFavorite,
Likes = data.Likes,
PlaybackPositionTicks = data.PlaybackPositionTicks,
PlayCount = data.PlayCount,
Rating = data.Rating,
Played = data.Played,
LastPlayedDate = data.LastPlayedDate,
ItemId = itemId,
Key = data.Key
};
}
private static int GetChildCount(Folder folder, User user, Dictionary<Guid, int>? childCountBatch)
{ {
// Right now this is too slow to calculate for top level folders on a per-user basis // Right now this is too slow to calculate for top level folders on a per-user basis
// Just return something so that apps that are expecting a value won't think the folders are empty // Just return something so that apps that are expecting a value won't think the folders are empty
@@ -642,13 +522,6 @@ namespace Emby.Server.Implementations.Dto
return Random.Shared.Next(1, 10); return Random.Shared.Next(1, 10);
} }
// Use pre-fetched batch data if available
if (childCountBatch is not null && childCountBatch.TryGetValue(folder.Id, out var count))
{
return count;
}
// Fall back to individual query for special cases (Series, Season, etc.)
return folder.GetChildCount(user); return folder.GetChildCount(user);
} }
@@ -942,8 +815,7 @@ namespace Emby.Server.Implementations.Dto
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="owner">The owner.</param> /// <param name="owner">The owner.</param>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="artistsBatch">Optional pre-fetched artist lookup shared across a batch of items.</param> private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options)
private void AttachBasicFields(BaseItemDto dto, BaseItem item, BaseItem? owner, DtoOptions options, IReadOnlyDictionary<string, MusicArtist[]>? artistsBatch = null)
{ {
if (options.ContainsField(ItemFields.DateCreated)) if (options.ContainsField(ItemFields.DateCreated))
{ {
@@ -1067,8 +939,6 @@ namespace Emby.Server.Implementations.Dto
dto.OriginalTitle = item.OriginalTitle; dto.OriginalTitle = item.OriginalTitle;
} }
dto.OriginalLanguage = item.OriginalLanguage;
if (options.ContainsField(ItemFields.ParentId)) if (options.ContainsField(ItemFields.ParentId))
{ {
dto.ParentId = item.DisplayParentId; dto.ParentId = item.DisplayParentId;
@@ -1149,15 +1019,6 @@ namespace Emby.Server.Implementations.Dto
{ {
dto.AlbumId = albumParent.Id; dto.AlbumId = albumParent.Id;
dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary); dto.AlbumPrimaryImageTag = GetTagAndFillBlurhash(dto, albumParent, ImageType.Primary);
if (albumParent.LUFS.HasValue)
{
// -18 LUFS reference, same as ReplayGain 2.0, compatible with ReplayGain 1.0
dto.AlbumNormalizationGain = -18f - albumParent.LUFS;
}
else if (albumParent.NormalizationGain.HasValue)
{
dto.AlbumNormalizationGain = albumParent.NormalizationGain;
}
} }
// if (options.ContainsField(ItemFields.MediaSourceCount)) // if (options.ContainsField(ItemFields.MediaSourceCount))
@@ -1190,8 +1051,7 @@ namespace Emby.Server.Implementations.Dto
// Include artists that are not in the database yet, e.g., just added via metadata editor // Include artists that are not in the database yet, e.g., just added via metadata editor
// var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList(); // var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
var artistsLookup = artistsBatch var artistsLookup = _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
?? _libraryManager.GetArtists([.. hasArtist.Artists.Where(e => !string.IsNullOrWhiteSpace(e))]);
dto.ArtistItems = hasArtist.Artists dto.ArtistItems = hasArtist.Artists
.Where(name => !string.IsNullOrWhiteSpace(name)) .Where(name => !string.IsNullOrWhiteSpace(name))
@@ -1225,8 +1085,7 @@ namespace Emby.Server.Implementations.Dto
// }) // })
// .ToList(); // .ToList();
var albumArtistsLookup = artistsBatch var albumArtistsLookup = _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(e))]);
?? _libraryManager.GetArtists([.. hasAlbumArtist.AlbumArtists.Where(e => !string.IsNullOrWhiteSpace(e))]);
dto.AlbumArtists = hasAlbumArtist.AlbumArtists dto.AlbumArtists = hasAlbumArtist.AlbumArtists
.Where(name => !string.IsNullOrWhiteSpace(name)) .Where(name => !string.IsNullOrWhiteSpace(name))
@@ -1264,6 +1123,11 @@ namespace Emby.Server.Implementations.Dto
} }
} }
if (options.ContainsField(ItemFields.Chapters))
{
dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
}
if (options.ContainsField(ItemFields.Trickplay)) if (options.ContainsField(ItemFields.Trickplay))
{ {
var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult(); var trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
@@ -1277,11 +1141,6 @@ namespace Emby.Server.Implementations.Dto
dto.ExtraType = video.ExtraType; dto.ExtraType = video.ExtraType;
} }
if (options.ContainsField(ItemFields.Chapters))
{
dto.Chapters = _chapterManager.GetChapters(item.Id).ToList();
}
if (options.ContainsField(ItemFields.MediaStreams)) if (options.ContainsField(ItemFields.MediaStreams))
{ {
// Add VideoInfo // Add VideoInfo

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Globalization;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
@@ -70,11 +69,6 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc /> /// <inheritdoc />
public IPAddress? RemoteEndPoint { get; } public IPAddress? RemoteEndPoint { get; }
/// <summary>
/// Gets or initializes the UI culture captured from the upgrade request.
/// </summary>
public CultureInfo? RequestUICulture { get; init; }
/// <inheritdoc /> /// <inheritdoc />
public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; } public Func<WebSocketMessageInfo, Task>? OnReceive { get; set; }
@@ -87,17 +81,6 @@ namespace Emby.Server.Implementations.HttpServer
/// <inheritdoc /> /// <inheritdoc />
public WebSocketState State => _socket.State; public WebSocketState State => _socket.State;
/// <inheritdoc />
public void ApplyRequestCulture()
{
if (RequestUICulture is null)
{
return;
}
CultureInfo.CurrentUICulture = RequestUICulture;
}
/// <inheritdoc /> /// <inheritdoc />
public async Task SendAsync(OutboundWebSocketMessage message, CancellationToken cancellationToken) public async Task SendAsync(OutboundWebSocketMessage message, CancellationToken cancellationToken)
{ {

View File

@@ -4,7 +4,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -48,18 +47,14 @@ namespace Emby.Server.Implementations.HttpServer
_logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress); _logger.LogInformation("WS {IP} request", context.Connection.RemoteIpAddress);
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false); WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync().ConfigureAwait(false);
var connection = new WebSocketConnection( var connection = new WebSocketConnection(
_loggerFactory.CreateLogger<WebSocketConnection>(), _loggerFactory.CreateLogger<WebSocketConnection>(),
webSocket, webSocket,
authorizationInfo, authorizationInfo,
context.GetNormalizedRemoteIP()) context.GetNormalizedRemoteIP())
{ {
RequestUICulture = CultureInfo.CurrentUICulture OnReceive = ProcessWebSocketMessageReceived
};
connection.OnReceive = result =>
{
connection.ApplyRequestCulture();
return ProcessWebSocketMessageReceived(result);
}; };
await using (connection.ConfigureAwait(false)) await using (connection.ConfigureAwait(false))
{ {

View File

@@ -21,7 +21,6 @@ namespace Emby.Server.Implementations.IO
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _configurationManager; private readonly IServerConfigurationManager _configurationManager;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly DotIgnoreIgnoreRule _dotIgnoreIgnoreRule;
/// <summary> /// <summary>
/// The file system watchers. /// The file system watchers.
@@ -48,23 +47,19 @@ namespace Emby.Server.Implementations.IO
/// <param name="configurationManager">The configuration manager.</param> /// <param name="configurationManager">The configuration manager.</param>
/// <param name="fileSystem">The filesystem.</param> /// <param name="fileSystem">The filesystem.</param>
/// <param name="appLifetime">The <see cref="IHostApplicationLifetime"/>.</param> /// <param name="appLifetime">The <see cref="IHostApplicationLifetime"/>.</param>
/// <param name="dotIgnoreIgnoreRule">The .ignore rule handler.</param>
public LibraryMonitor( public LibraryMonitor(
ILogger<LibraryMonitor> logger, ILogger<LibraryMonitor> logger,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IServerConfigurationManager configurationManager, IServerConfigurationManager configurationManager,
IFileSystem fileSystem, IFileSystem fileSystem,
IHostApplicationLifetime appLifetime, IHostApplicationLifetime appLifetime)
DotIgnoreIgnoreRule dotIgnoreIgnoreRule)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_configurationManager = configurationManager; _configurationManager = configurationManager;
_fileSystem = fileSystem; _fileSystem = fileSystem;
_dotIgnoreIgnoreRule = dotIgnoreIgnoreRule;
appLifetime.ApplicationStarted.Register(Start); appLifetime.ApplicationStarted.Register(Start);
appLifetime.ApplicationStopping.Register(Stop);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -358,7 +353,7 @@ namespace Emby.Server.Implementations.IO
} }
var fileInfo = _fileSystem.GetFileSystemInfo(path); var fileInfo = _fileSystem.GetFileSystemInfo(path);
if (_dotIgnoreIgnoreRule.ShouldIgnore(fileInfo, null)) if (DotIgnoreIgnoreRule.IsIgnored(fileInfo, null))
{ {
return; return;
} }

View File

@@ -586,12 +586,6 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc /> /// <inheritdoc />
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, string searchPattern, IReadOnlyList<string>? extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
if (!Directory.Exists(path))
{
_logger.LogWarning("Directory does not exist: {Path}", path);
return [];
}
var enumerationOptions = GetEnumerationOptions(recursive); var enumerationOptions = GetEnumerationOptions(recursive);
// On linux and macOS the search pattern is case-sensitive // On linux and macOS the search pattern is case-sensitive

View File

@@ -267,24 +267,22 @@ namespace Emby.Server.Implementations.Images
{ {
var image = item.GetImageInfo(type, 0); var image = item.GetImageInfo(type, 0);
if (image is null) if (image is not null)
{ {
return GetItemsWithImages(item).Count is not 0; if (!image.IsLocalFile)
} {
return false;
}
if (!image.IsLocalFile) if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path))
{ {
return false; return false;
} }
if (!FileSystem.ContainsSubPath(item.GetInternalMetadataPath(), image.Path)) if (!HasChangedByDate(item, image))
{ {
return false; return false;
} }
if (!HasChangedByDate(item, image))
{
return false;
} }
return true; return true;

View File

@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Images
includeItemTypes = new[] { BaseItemKind.Series }; includeItemTypes = new[] { BaseItemKind.Series };
break; break;
case CollectionType.music: case CollectionType.music:
includeItemTypes = new[] { BaseItemKind.MusicArtist }; // Music albums usually don't have dedicated backdrops, so use artist instead includeItemTypes = new[] { BaseItemKind.MusicAlbum };
break; break;
case CollectionType.musicvideos: case CollectionType.musicvideos:
includeItemTypes = new[] { BaseItemKind.MusicVideo }; includeItemTypes = new[] { BaseItemKind.MusicVideo };

View File

@@ -1,8 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using BitFaster.Caching.Lru;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@@ -17,36 +15,22 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
{ {
private static readonly bool IsWindows = OperatingSystem.IsWindows(); private static readonly bool IsWindows = OperatingSystem.IsWindows();
private readonly FastConcurrentLru<string, IgnoreFileCacheEntry> _directoryCache; private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
private readonly FastConcurrentLru<string, ParsedIgnoreCacheEntry> _rulesCache;
/// <summary>
/// Initializes a new instance of the <see cref="DotIgnoreIgnoreRule"/> class.
/// </summary>
public DotIgnoreIgnoreRule()
{ {
var cacheSize = Math.Max(100, Environment.ProcessorCount * 100); for (var current = directory; current is not null; current = current.Parent)
_directoryCache = new FastConcurrentLru<string, IgnoreFileCacheEntry>( {
Environment.ProcessorCount, var ignorePath = Path.Join(current.FullName, ".ignore");
cacheSize, if (File.Exists(ignorePath))
StringComparer.Ordinal); {
_rulesCache = new FastConcurrentLru<string, ParsedIgnoreCacheEntry>( return new FileInfo(ignorePath);
Environment.ProcessorCount, }
Math.Max(32, cacheSize / 4), }
StringComparer.Ordinal);
return null;
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnoredInternal(fileInfo, parent); public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnored(fileInfo, parent);
/// <summary>
/// Clears the directory lookup cache. The parsed rules cache is not cleared
/// as it validates file modification time on each access.
/// </summary>
public void ClearDirectoryCache()
{
_directoryCache.Clear();
}
/// <summary> /// <summary>
/// Checks whether or not the file is ignored. /// Checks whether or not the file is ignored.
@@ -54,38 +38,40 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
/// <param name="fileInfo">The file information.</param> /// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent BaseItem.</param> /// <param name="parent">The parent BaseItem.</param>
/// <returns>True if the file should be ignored.</returns> /// <returns>True if the file should be ignored.</returns>
public bool IsIgnoredInternal(FileSystemMetadata fileInfo, BaseItem? parent) public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
{ {
var searchDirectory = fileInfo.IsDirectory var searchDirectory = fileInfo.IsDirectory
? fileInfo.FullName ? new DirectoryInfo(fileInfo.FullName)
: Path.GetDirectoryName(fileInfo.FullName); : new DirectoryInfo(Path.GetDirectoryName(fileInfo.FullName) ?? string.Empty);
if (string.IsNullOrEmpty(searchDirectory)) if (string.IsNullOrEmpty(searchDirectory.FullName))
{ {
return false; return false;
} }
var ignoreFile = FindIgnoreFileCached(searchDirectory); var ignoreFile = FindIgnoreFile(searchDirectory);
if (ignoreFile is null) if (ignoreFile is null)
{ {
return false; return false;
} }
var parsedEntry = GetParsedRules(ignoreFile); // Fast path in case the ignore files isn't a symlink and is empty
if (parsedEntry is null) if (ignoreFile.LinkTarget is null && ignoreFile.Length == 0)
{
// File was deleted after we cached the path - clear the directory cache entry and return false
_directoryCache.TryRemove(searchDirectory, out _);
return false;
}
// Empty file means ignore everything
if (parsedEntry.IsEmpty)
{ {
// Ignore directory if we just have the file
return true; return true;
} }
return parsedEntry.Rules.IsIgnored(GetPathToCheck(fileInfo.FullName, fileInfo.IsDirectory)); var content = GetFileContent(ignoreFile);
return string.IsNullOrWhiteSpace(content)
|| CheckIgnoreRules(fileInfo.FullName, content, fileInfo.IsDirectory);
}
private static bool CheckIgnoreRules(string path, string ignoreFileContent, bool isDirectory)
{
// If file has content, base ignoring off the content .gitignore-style rules
var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return CheckIgnoreRules(path, rules, isDirectory);
} }
/// <summary> /// <summary>
@@ -131,8 +117,8 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
return true; return true;
} }
// Mitigate the problem of the Ignore library not handling Windows paths correctly. // Mitigate the problem of the Ignore library not handling Windows paths correctly.
// See https://github.com/jellyfin/jellyfin/issues/15484 // See https://github.com/jellyfin/jellyfin/issues/15484
var pathToCheck = normalizePath ? path.NormalizePath('/') : path; var pathToCheck = normalizePath ? path.NormalizePath('/') : path;
// Add trailing slash for directories to match "folder/" // Add trailing slash for directories to match "folder/"
@@ -144,196 +130,11 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
return ignore.IsIgnored(pathToCheck); return ignore.IsIgnored(pathToCheck);
} }
private FileInfo? FindIgnoreFileCached(string directory) private static string GetFileContent(FileInfo ignoreFile)
{ {
// Check if we have a cached result for this directory ignoreFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile;
if (_directoryCache.TryGet(directory, out var cached)) return ignoreFile.Exists
{ ? File.ReadAllText(ignoreFile.FullName)
return cached.IgnoreFileDirectory is null : string.Empty;
? null
: new FileInfo(Path.Join(cached.IgnoreFileDirectory, ".ignore"));
}
DirectoryInfo startDir;
try
{
startDir = new DirectoryInfo(directory);
}
catch (ArgumentException)
{
return null;
}
// Walk up the directory tree to find .ignore file using DirectoryInfo.Parent
var checkedDirs = new List<string> { directory };
for (var current = startDir; current is not null; current = current.Parent)
{
var currentPath = current.FullName;
// Check if this intermediate directory is cached
if (current != startDir && _directoryCache.TryGet(currentPath, out var parentCached))
{
// Cache the result for all directories we checked
var entry = new IgnoreFileCacheEntry(parentCached.IgnoreFileDirectory);
foreach (var dir in checkedDirs)
{
_directoryCache.AddOrUpdate(dir, entry);
}
return parentCached.IgnoreFileDirectory is null
? null
: new FileInfo(Path.Join(parentCached.IgnoreFileDirectory, ".ignore"));
}
var ignoreFile = new FileInfo(Path.Join(currentPath, ".ignore"));
if (ignoreFile.Exists)
{
// Cache for all directories we checked
var entry = new IgnoreFileCacheEntry(currentPath);
foreach (var dir in checkedDirs)
{
_directoryCache.AddOrUpdate(dir, entry);
}
return ignoreFile;
}
if (current != startDir)
{
checkedDirs.Add(currentPath);
}
}
// No .ignore file found - cache null result for all directories
var nullEntry = new IgnoreFileCacheEntry((string?)null);
foreach (var dir in checkedDirs)
{
_directoryCache.AddOrUpdate(dir, nullEntry);
}
return null;
}
private ParsedIgnoreCacheEntry? GetParsedRules(FileInfo ignoreFile)
{
if (!ignoreFile.Exists)
{
_rulesCache.TryRemove(ignoreFile.FullName, out _);
return null;
}
var lastModified = ignoreFile.LastWriteTimeUtc;
var fileLength = ignoreFile.Length;
var key = ignoreFile.FullName;
// Check cache
if (_rulesCache.TryGet(key, out var cached))
{
if (cached.FileLastModified == lastModified && cached.FileLength == fileLength)
{
return cached;
}
// Stale - need to reparse
_rulesCache.TryRemove(key, out _);
}
// Parse the file
var parsedEntry = ParseIgnoreFile(ignoreFile, lastModified, fileLength);
_rulesCache.AddOrUpdate(key, parsedEntry);
return parsedEntry;
}
private static ParsedIgnoreCacheEntry ParseIgnoreFile(FileInfo ignoreFile, DateTime lastModified, long fileLength)
{
if (ignoreFile.LinkTarget is null && fileLength == 0)
{
return new ParsedIgnoreCacheEntry
{
Rules = new Ignore.Ignore(),
FileLastModified = lastModified,
FileLength = fileLength,
IsEmpty = true
};
}
// Resolve symlinks
var resolvedFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile;
if (!resolvedFile.Exists)
{
return new ParsedIgnoreCacheEntry
{
Rules = new Ignore.Ignore(),
FileLastModified = lastModified,
FileLength = fileLength,
IsEmpty = true
};
}
var content = File.ReadAllText(resolvedFile.FullName);
if (string.IsNullOrWhiteSpace(content))
{
return new ParsedIgnoreCacheEntry
{
Rules = new Ignore.Ignore(),
FileLastModified = lastModified,
FileLength = fileLength,
IsEmpty = true
};
}
var rules = content.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var ignore = new Ignore.Ignore();
var validRulesAdded = 0;
foreach (var rule in rules)
{
try
{
ignore.Add(rule);
validRulesAdded++;
}
catch (RegexParseException)
{
// Ignore invalid patterns
}
}
// No valid rules means treat as empty (ignore all)
return new ParsedIgnoreCacheEntry
{
Rules = ignore,
FileLastModified = lastModified,
FileLength = fileLength,
IsEmpty = validRulesAdded == 0
};
}
private static string GetPathToCheck(string path, bool isDirectory)
{
// Normalize Windows paths
var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
// Add trailing slash for directories to match "folder/"
if (isDirectory)
{
pathToCheck = string.Concat(pathToCheck.AsSpan().TrimEnd('/'), "/");
}
return pathToCheck;
}
private readonly record struct IgnoreFileCacheEntry(string? IgnoreFileDirectory);
private sealed class ParsedIgnoreCacheEntry
{
public required Ignore.Ignore Rules { get; init; }
public required DateTime FileLastModified { get; init; }
public required long FileLength { get; init; }
public required bool IsEmpty { get; init; }
} }
} }

View File

@@ -31,20 +31,6 @@ namespace Emby.Server.Implementations.Library
"**/*.sample.?????", "**/*.sample.?????",
"**/sample/*", "**/sample/*",
// Avoid adding Hungarian sample files
// https://github.com/jellyfin/jellyfin/issues/16237
"**/minta.?",
"**/minta.??",
"**/minta.???", // Matches minta.mkv
"**/minta.????", // Matches minta.webm
"**/minta.?????",
"**/*.minta.?",
"**/*.minta.??",
"**/*.minta.???",
"**/*.minta.????",
"**/*.minta.?????",
"**/minta/*",
// Directories // Directories
"**/metadata/**", "**/metadata/**",
"**/metadata", "**/metadata",

View File

@@ -13,7 +13,6 @@ using System.Threading.Tasks;
using BitFaster.Caching.Lru; using BitFaster.Caching.Lru;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using Emby.Naming.Video;
using Emby.Server.Implementations.Library.Resolvers; 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;
@@ -31,17 +30,18 @@ using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.MediaSegments;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@@ -77,18 +77,12 @@ namespace Emby.Server.Implementations.Library
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly IItemRepository _itemRepository; private readonly IItemRepository _itemRepository;
private readonly IItemPersistenceService _persistenceService;
private readonly INextUpService _nextUpService;
private readonly IItemCountService _countService;
private readonly ILinkedChildrenService _linkedChildrenService;
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions; private readonly NamingOptions _namingOptions;
private readonly IPeopleRepository _peopleRepository; private readonly IPeopleRepository _peopleRepository;
private readonly ExtraResolver _extraResolver; private readonly ExtraResolver _extraResolver;
private readonly IPathManager _pathManager; private readonly IPathManager _pathManager;
private readonly FastConcurrentLru<Guid, BaseItem> _cache; private readonly FastConcurrentLru<Guid, BaseItem> _cache;
private readonly DotIgnoreIgnoreRule _dotIgnoreIgnoreRule;
private readonly IMediaStreamRepository _mediaStreamRepository;
/// <summary> /// <summary>
/// The _root folder sync lock. /// The _root folder sync lock.
@@ -121,17 +115,11 @@ namespace Emby.Server.Implementations.Library
/// <param name="userViewManagerFactory">The user view manager.</param> /// <param name="userViewManagerFactory">The user view manager.</param>
/// <param name="mediaEncoder">The media encoder.</param> /// <param name="mediaEncoder">The media encoder.</param>
/// <param name="itemRepository">The item repository.</param> /// <param name="itemRepository">The item repository.</param>
/// <param name="persistenceService">The item persistence service.</param>
/// <param name="nextUpService">The next up service.</param>
/// <param name="countService">The item count service.</param>
/// <param name="linkedChildrenService">The linked children service.</param>
/// <param name="imageProcessor">The image processor.</param> /// <param name="imageProcessor">The image processor.</param>
/// <param name="namingOptions">The naming options.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
/// <param name="peopleRepository">The people repository.</param> /// <param name="peopleRepository">The people repository.</param>
/// <param name="pathManager">The path manager.</param> /// <param name="pathManager">The path manager.</param>
/// <param name="dotIgnoreIgnoreRule">The .ignore rule handler.</param>
/// <param name="mediaStreamRepository">The media stream repository.</param>
public LibraryManager( public LibraryManager(
IServerApplicationHost appHost, IServerApplicationHost appHost,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
@@ -145,17 +133,11 @@ namespace Emby.Server.Implementations.Library
Lazy<IUserViewManager> userViewManagerFactory, Lazy<IUserViewManager> userViewManagerFactory,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IItemRepository itemRepository, IItemRepository itemRepository,
IItemPersistenceService persistenceService,
INextUpService nextUpService,
IItemCountService countService,
ILinkedChildrenService linkedChildrenService,
IImageProcessor imageProcessor, IImageProcessor imageProcessor,
NamingOptions namingOptions, NamingOptions namingOptions,
IDirectoryService directoryService, IDirectoryService directoryService,
IPeopleRepository peopleRepository, IPeopleRepository peopleRepository,
IPathManager pathManager, IPathManager pathManager)
DotIgnoreIgnoreRule dotIgnoreIgnoreRule,
IMediaStreamRepository mediaStreamRepository)
{ {
_appHost = appHost; _appHost = appHost;
_logger = loggerFactory.CreateLogger<LibraryManager>(); _logger = loggerFactory.CreateLogger<LibraryManager>();
@@ -169,10 +151,6 @@ namespace Emby.Server.Implementations.Library
_userViewManagerFactory = userViewManagerFactory; _userViewManagerFactory = userViewManagerFactory;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_itemRepository = itemRepository; _itemRepository = itemRepository;
_persistenceService = persistenceService;
_nextUpService = nextUpService;
_countService = countService;
_linkedChildrenService = linkedChildrenService;
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_cache = new FastConcurrentLru<Guid, BaseItem>(_configurationManager.Configuration.CacheSize); _cache = new FastConcurrentLru<Guid, BaseItem>(_configurationManager.Configuration.CacheSize);
@@ -180,13 +158,10 @@ namespace Emby.Server.Implementations.Library
_namingOptions = namingOptions; _namingOptions = namingOptions;
_peopleRepository = peopleRepository; _peopleRepository = peopleRepository;
_pathManager = pathManager; _pathManager = pathManager;
_dotIgnoreIgnoreRule = dotIgnoreIgnoreRule;
_extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService); _extraResolver = new ExtraResolver(loggerFactory.CreateLogger<ExtraResolver>(), namingOptions, directoryService);
_configurationManager.ConfigurationUpdated += ConfigurationUpdated; _configurationManager.ConfigurationUpdated += ConfigurationUpdated;
_mediaStreamRepository = mediaStreamRepository;
RecordConfigurationValues(_configurationManager.Configuration); RecordConfigurationValues(_configurationManager.Configuration);
} }
@@ -352,17 +327,9 @@ namespace Emby.Server.Implementations.Library
DeleteItem(item, options, parent, notifyParentItem); DeleteItem(item, options, parent, notifyParentItem);
} }
public void DeleteItemsUnsafeFast(IReadOnlyCollection<BaseItem> items, bool deleteSourceFiles = false) public void DeleteItemsUnsafeFast(IEnumerable<BaseItem> items)
{ {
if (items.Count == 0) var pathMaps = items.Select(e => (Item: e, InternalPath: GetInternalMetadataPaths(e), DeletePaths: e.GetDeletePaths())).ToArray();
{
return;
}
var pathMaps = items.Select(e =>
(Item: e,
InternalPath: GetInternalMetadataPaths(e),
DeletePaths: deleteSourceFiles ? e.GetDeletePaths() : [])).ToArray();
foreach (var (item, internalPaths, pathsToDelete) in pathMaps) foreach (var (item, internalPaths, pathsToDelete) in pathMaps)
{ {
@@ -396,7 +363,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
_persistenceService.DeleteItem([.. pathMaps.Select(f => f.Item.Id)]); _itemRepository.DeleteItem([.. pathMaps.Select(f => f.Item.Id)]);
} }
public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem) public void DeleteItem(BaseItem item, DeleteOptions options, BaseItem parent, bool notifyParentItem)
@@ -439,99 +406,6 @@ namespace Emby.Server.Implementations.Library
item.Id); item.Id);
} }
// If deleting a primary version video, clear PrimaryVersionId from alternate versions
// OwnerId check: items with OwnerId set are alternate versions or extras, not primaries
if (item is Video video && !video.PrimaryVersionId.HasValue && video.OwnerId.IsEmpty())
{
var localAlternateIds = GetLocalAlternateVersionIds(video).ToHashSet();
var allAlternateVersions = localAlternateIds
.Concat(GetLinkedAlternateVersions(video).Select(v => v.Id))
.Distinct()
.Select(id => GetItemById(id))
.OfType<Video>()
.ToList();
// Partition alternates by whether their files still exist on disk
var alternateVersions = new List<Video>();
var missingAlternates = new List<Video>();
foreach (var alt in allAlternateVersions)
{
if (!string.IsNullOrEmpty(alt.Path) && !_fileSystem.FileExists(alt.Path))
{
missingAlternates.Add(alt);
}
else
{
alternateVersions.Add(alt);
}
}
// Delete alternates whose files no longer exist to avoid ghost items.
// Clear PrimaryVersionId first so DeleteItem doesn't try to update the primary being deleted.
foreach (var missing in missingAlternates)
{
_logger.LogInformation(
"Deleting missing alternate version {Name} ({Path})",
missing.Name ?? "Unknown name",
missing.Path ?? string.Empty);
missing.SetPrimaryVersionId(null);
missing.OwnerId = Guid.Empty;
missing.LocalAlternateVersions = [];
missing.LinkedAlternateVersions = [];
DeleteItem(missing, new DeleteOptions { DeleteFileLocation = false }, false);
}
if (alternateVersions.Count > 0)
{
_logger.LogInformation(
"Clearing PrimaryVersionId from {Count} alternate versions of {Name}",
alternateVersions.Count,
item.Name ?? "Unknown name");
// Promote the first alternate version to be the new primary
var newPrimary = alternateVersions[0];
newPrimary.SetPrimaryVersionId(null);
newPrimary.OwnerId = Guid.Empty;
// Transfer alternate version arrays from old primary to new primary
// so UpdateToRepositoryAsync creates correct LinkedChildren entries
newPrimary.LocalAlternateVersions = video.LocalAlternateVersions
.Where(p => !string.Equals(p, newPrimary.Path, StringComparison.OrdinalIgnoreCase))
.ToArray();
newPrimary.LinkedAlternateVersions = video.LinkedAlternateVersions
.Where(lc => !lc.ItemId.HasValue || !lc.ItemId.Value.Equals(newPrimary.Id))
.ToArray();
newPrimary.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
// Re-route playlist/collection references from deleted primary to new primary
RerouteLinkedChildReferencesAsync(video.Id, newPrimary.Id).GetAwaiter().GetResult();
// Update remaining alternates to point to new primary
foreach (var alternate in alternateVersions.Skip(1))
{
alternate.SetPrimaryVersionId(newPrimary.Id);
// Only set OwnerId for local alternates; linked alternates are independent items
alternate.OwnerId = localAlternateIds.Contains(alternate.Id) ? newPrimary.Id : Guid.Empty;
alternate.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
}
}
}
else if (item is Video alternateVideo && alternateVideo.PrimaryVersionId.HasValue)
{
// If deleting an alternate version, re-route references to its primary
RerouteLinkedChildReferencesAsync(alternateVideo.Id, alternateVideo.PrimaryVersionId.Value).GetAwaiter().GetResult();
// Remove deleted alternate from primary's LinkedAlternateVersions
if (GetItemById(alternateVideo.PrimaryVersionId.Value) is Video primaryVideo)
{
primaryVideo.LinkedAlternateVersions = primaryVideo.LinkedAlternateVersions
.Where(lc => !lc.ItemId.HasValue || !lc.ItemId.Value.Equals(alternateVideo.Id))
.ToArray();
primaryVideo.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
}
}
var children = item.IsFolder var children = item.IsFolder
? ((Folder)item).GetRecursiveChildren(false) ? ((Folder)item).GetRecursiveChildren(false)
: []; : [];
@@ -576,7 +450,7 @@ namespace Emby.Server.Implementations.Library
item.SetParent(null); item.SetParent(null);
_persistenceService.DeleteItem([item.Id, .. children.Select(f => f.Id)]); _itemRepository.DeleteItem([item.Id, .. children.Select(f => f.Id)]);
_cache.TryRemove(item.Id, out _); _cache.TryRemove(item.Id, out _);
foreach (var child in children) foreach (var child in children)
{ {
@@ -702,9 +576,6 @@ namespace Emby.Server.Implementations.Library
// Trickplay // Trickplay
list.Add(_pathManager.GetTrickplayDirectory(video)); list.Add(_pathManager.GetTrickplayDirectory(video));
// Chapter Images
list.Add(_pathManager.GetChapterImageFolderPath(video));
// Subtitles and attachments // Subtitles and attachments
foreach (var mediaSource in item.GetMediaSources(false)) foreach (var mediaSource in item.GetMediaSources(false))
{ {
@@ -786,99 +657,8 @@ namespace Emby.Server.Implementations.Library
return key.GetMD5(); return key.GetMD5();
} }
public BaseItem? ResolvePath( public BaseItem? ResolvePath(FileSystemMetadata fileInfo, Folder? parent = null, IDirectoryService? directoryService = null)
FileSystemMetadata fileInfo, => ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent);
Folder? parent = null,
IDirectoryService? directoryService = null,
CollectionType? collectionType = null)
=> ResolvePath(fileInfo, directoryService ?? new DirectoryService(_fileSystem), null, parent, collectionType);
private void SetAdditionalPartsFromStack(Video altVideo, string path)
{
if (altVideo.AdditionalParts is { Length: > 0 })
{
return;
}
var directory = Path.GetDirectoryName(path);
if (string.IsNullOrEmpty(directory))
{
return;
}
IEnumerable<FileSystemMetadata> siblings;
try
{
siblings = _fileSystem.GetFiles(directory);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to enumerate siblings to detect stack for {Path}", path);
return;
}
var stacks = StackResolver.Resolve(siblings, _namingOptions);
foreach (var stack in stacks)
{
if (stack.Files.Count > 1
&& string.Equals(stack.Files[0], path, StringComparison.OrdinalIgnoreCase))
{
altVideo.AdditionalParts = stack.Files.Skip(1).ToArray();
return;
}
}
}
/// <inheritdoc />
public Video? ResolveAlternateVersion(string path, Type expectedVideoType, Folder? parent, CollectionType? collectionType)
{
// Clean up any existing item saved with wrong type (e.g. Video instead of Movie).
// This happens when items were previously resolved without proper type context
// in mixed-content libraries where collectionType is null.
var expectedId = GetNewItemId(path, expectedVideoType);
if (expectedVideoType != typeof(Video))
{
var wrongTypeId = GetNewItemId(path, typeof(Video));
if (!wrongTypeId.Equals(expectedId) && GetItemById(wrongTypeId) is Video wrongTypeItem)
{
_logger.LogInformation(
"Removing alternate version with wrong type {WrongType}, expected {ExpectedType}: {Path}",
wrongTypeItem.GetType().Name,
expectedVideoType.Name,
path);
DeleteItem(wrongTypeItem, new DeleteOptions { DeleteFileLocation = false });
}
}
var resolved = ResolvePath(
_fileSystem.GetFileSystemInfo(path),
parent,
collectionType: collectionType) as Video;
if (resolved is null)
{
return null;
}
// Ensure the alternate version has the same concrete type as the primary video.
// ResolvePath may return a generic Video for files in mixed-content libraries
// where collectionType is null, even though the primary is a Movie/Episode/etc.
if (resolved.GetType() != expectedVideoType)
{
if (Activator.CreateInstance(expectedVideoType) is Video correctVideo)
{
correctVideo.Path = resolved.Path;
correctVideo.Name = resolved.Name;
correctVideo.VideoType = resolved.VideoType;
correctVideo.ProductionYear = resolved.ProductionYear;
correctVideo.ExtraType = resolved.ExtraType;
resolved = correctVideo;
}
}
resolved.Id = expectedId;
return resolved;
}
private BaseItem? ResolvePath( private BaseItem? ResolvePath(
FileSystemMetadata fileInfo, FileSystemMetadata fileInfo,
@@ -1261,7 +1041,7 @@ namespace Emby.Server.Implementations.Library
public IReadOnlyDictionary<string, MusicArtist[]> GetArtists(IReadOnlyList<string> names) public IReadOnlyDictionary<string, MusicArtist[]> GetArtists(IReadOnlyList<string> names)
{ {
return _linkedChildrenService.FindArtists(names); return _itemRepository.FindArtists(names);
} }
public MusicArtist GetArtist(string name, DtoOptions options) public MusicArtist GetArtist(string name, DtoOptions options)
@@ -1351,7 +1131,6 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken) public async Task ValidateMediaLibraryInternal(IProgress<double> progress, CancellationToken cancellationToken)
{ {
IsScanRunning = true; IsScanRunning = true;
ClearIgnoreRuleCache();
LibraryMonitor.Stop(); LibraryMonitor.Stop();
try try
@@ -1360,7 +1139,6 @@ namespace Emby.Server.Implementations.Library
} }
finally finally
{ {
ClearIgnoreRuleCache();
LibraryMonitor.Start(); LibraryMonitor.Start();
IsScanRunning = false; IsScanRunning = false;
} }
@@ -1368,7 +1146,6 @@ namespace Emby.Server.Implementations.Library
public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false) public async Task ValidateTopLibraryFolders(CancellationToken cancellationToken, bool removeRoot = false)
{ {
ClearIgnoreRuleCache();
RootFolder.Children = null; RootFolder.Children = null;
await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false);
@@ -1409,16 +1186,8 @@ namespace Emby.Server.Implementations.Library
if (toDelete.Count > 0) if (toDelete.Count > 0)
{ {
_persistenceService.DeleteItem(toDelete.ToArray()); _itemRepository.DeleteItem(toDelete.ToArray());
} }
ClearIgnoreRuleCache();
}
/// <inheritdoc />
public void ClearIgnoreRuleCache()
{
_dotIgnoreIgnoreRule.ClearDirectoryCache();
} }
private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken) private async Task PerformLibraryValidation(IProgress<double> progress, CancellationToken cancellationToken)
@@ -1493,7 +1262,7 @@ namespace Emby.Server.Implementations.Library
progress.Report(percent * 100); progress.Report(percent * 100);
} }
_persistenceService.UpdateInheritedValues(); _itemRepository.UpdateInheritedValues();
progress.Report(100); progress.Report(100);
} }
@@ -1652,7 +1421,14 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User, allowExternalContent); AddUserToQuery(query, query.User, allowExternalContent);
} }
return _itemRepository.GetItemList(query); var itemList = _itemRepository.GetItemList(query);
var user = query.User;
if (user is not null)
{
return itemList.Where(i => i.IsVisible(user)).ToList();
}
return itemList;
} }
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query) public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query)
@@ -1676,7 +1452,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User); AddUserToQuery(query, query.User);
} }
return _countService.GetCount(query); return _itemRepository.GetCount(query);
} }
public ItemCounts GetItemCounts(InternalItemsQuery query) public ItemCounts GetItemCounts(InternalItemsQuery query)
@@ -1695,30 +1471,7 @@ namespace Emby.Server.Implementations.Library
AddUserToQuery(query, query.User); AddUserToQuery(query, query.User);
} }
return _countService.GetItemCounts(query); return _itemRepository.GetItemCounts(query);
}
/// <inheritdoc/>
public ItemCounts GetItemCountsForNameItem(BaseItemKind kind, Guid id, BaseItemKind[] relatedItemKinds, User? user)
{
var query = new InternalItemsQuery(user);
if (user is not null)
{
AddUserToQuery(query, user);
}
return _countService.GetItemCountsForNameItem(kind, id, relatedItemKinds, query);
}
public Dictionary<Guid, int> GetChildCountBatch(IReadOnlyList<Guid> parentIds, Guid? userId)
{
return _countService.GetChildCountBatch(parentIds, userId);
}
/// <inheritdoc/>
public Dictionary<Guid, (int Played, int Total)> GetPlayedAndTotalCountBatch(IReadOnlyList<Guid> folderIds, User user)
{
return _countService.GetPlayedAndTotalCountBatch(folderIds, user);
} }
public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents) public IReadOnlyList<BaseItem> GetItemList(InternalItemsQuery query, List<BaseItem> parents)
@@ -1763,17 +1516,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
return _nextUpService.GetNextUpSeriesKeys(query, dateCutoff); return _itemRepository.GetNextUpSeriesKeys(query, dateCutoff);
}
/// <inheritdoc />
public IReadOnlyDictionary<string, MediaBrowser.Controller.Persistence.NextUpEpisodeBatchResult> GetNextUpEpisodesBatch(
InternalItemsQuery query,
IReadOnlyList<string> seriesKeys,
bool includeSpecials,
bool includeWatchedForRewatching)
{
return _nextUpService.GetNextUpEpisodesBatch(query, seriesKeys, includeSpecials, includeWatchedForRewatching);
} }
public QueryResult<BaseItem> QueryItems(InternalItemsQuery query) public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
@@ -1940,25 +1683,6 @@ namespace Emby.Server.Implementations.Library
query.TopParentIds = [Guid.NewGuid()]; query.TopParentIds = [Guid.NewGuid()];
} }
} }
else if (parents.Count == 1 && parents.First() is Folder folder
&& (folder is Playlist || folder is BoxSet)
&& folder.LinkedChildren.Length > 0)
{
// Playlists and BoxSets store their contents in LinkedChildren and never
// populate AncestorIds for those items, so a recursive AncestorIds query
// would return zero rows. Resolve to the linked child IDs up front and
// route through the existing indexed ItemIds filter.
query.ItemIds = folder.LinkedChildren
.Where(lc => lc.ItemId.HasValue && !lc.ItemId.Value.IsEmpty())
.Select(lc => lc.ItemId!.Value)
.ToArray();
// Empty linked-children should still return empty rather than scanning everything.
if (query.ItemIds.Length == 0)
{
query.ItemIds = [Guid.NewGuid()];
}
}
else else
{ {
// We need to be able to query from any arbitrary ancestor up the tree // We need to be able to query from any arbitrary ancestor up the tree
@@ -1976,11 +1700,6 @@ namespace Emby.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true) private void AddUserToQuery(InternalItemsQuery query, User user, bool allowExternalContent = true)
{ {
if (query.User is null)
{
query.SetUser(user);
}
if (query.AncestorIds.Length == 0 && if (query.AncestorIds.Length == 0 &&
query.ParentId.IsEmpty() && query.ParentId.IsEmpty() &&
query.ChannelIds.Count == 0 && query.ChannelIds.Count == 0 &&
@@ -2006,15 +1725,6 @@ namespace Emby.Server.Implementations.Library
} }
} }
/// <inheritdoc/>
public void ConfigureUserAccess(InternalItemsQuery query, User user)
{
ArgumentNullException.ThrowIfNull(query);
ArgumentNullException.ThrowIfNull(user);
AddUserToQuery(query, user);
}
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)
@@ -2179,44 +1889,6 @@ namespace Emby.Server.Implementations.Library
return video; return video;
} }
/// <inheritdoc />
public IEnumerable<Guid> GetLocalAlternateVersionIds(Video video)
{
ArgumentNullException.ThrowIfNull(video);
var linkedIds = _linkedChildrenService.GetLinkedChildrenIds(video.Id, (int)MediaBrowser.Controller.Entities.LinkedChildType.LocalAlternateVersion);
if (linkedIds.Count > 0)
{
return linkedIds;
}
return [];
}
/// <inheritdoc />
public IEnumerable<Video> GetLinkedAlternateVersions(Video video)
{
ArgumentNullException.ThrowIfNull(video);
var linkedIds = _linkedChildrenService.GetLinkedChildrenIds(video.Id, (int)MediaBrowser.Controller.Entities.LinkedChildType.LinkedAlternateVersion);
if (linkedIds.Count > 0)
{
return linkedIds
.Select(id => GetItemById(id))
.Where(i => i is not null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
return [];
}
/// <inheritdoc />
public void UpsertLinkedChild(Guid parentId, Guid childId, MediaBrowser.Controller.Entities.LinkedChildType childType)
{
_linkedChildrenService.UpsertLinkedChild(parentId, childId, childType);
}
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder) public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User? user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder)
{ {
@@ -2321,48 +1993,9 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc /> /// <inheritdoc />
public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken) public void CreateItems(IReadOnlyList<BaseItem> items, BaseItem? parent, CancellationToken cancellationToken)
{ {
// Resolve and add any local alternate version items that don't exist yet _itemRepository.SaveItems(items, cancellationToken);
// This ensures they exist in the database when LinkedChildren are processed
var allItems = new List<BaseItem>(items);
var parentFolder = parent as Folder;
var parentCollectionType = parent is not null ? GetTopFolderContentType(parent) : null;
foreach (var item in items) foreach (var item in items)
{
if (item is Video video && video.LocalAlternateVersions.Length > 0)
{
var videoType = video.GetType();
foreach (var path in video.LocalAlternateVersions)
{
if (string.IsNullOrEmpty(path))
{
continue;
}
// Use the primary video's type for ID calculation to ensure consistency
var altId = GetNewItemId(path, videoType);
if (GetItemById(altId) is null && !allItems.Any(i => i.Id.Equals(altId)))
{
// Alternate version doesn't exist, resolve and create it
// ensuring it has the same type as the primary video
var altVideo = ResolveAlternateVersion(path, videoType, parentFolder, parentCollectionType);
if (altVideo is not null)
{
altVideo.OwnerId = video.Id;
altVideo.SetPrimaryVersionId(video.Id);
// ResolveAlternateVersion only sees the alternate's primary file.
// If the alternate is itself a stack (e.g. 1080p part1 + part2),
// detect its parts from sibling files so its AdditionalParts persist.
SetAdditionalPartsFromStack(altVideo, path);
allItems.Add(altVideo);
}
}
}
}
}
_persistenceService.SaveItems(allItems, cancellationToken);
foreach (var item in allItems)
{ {
RegisterItem(item); RegisterItem(item);
} }
@@ -2511,7 +2144,7 @@ namespace Emby.Server.Implementations.Library
item.ValidateImages(); item.ValidateImages();
await _persistenceService.SaveImagesAsync(item).ConfigureAwait(false); await _itemRepository.SaveImagesAsync(item).ConfigureAwait(false);
RegisterItem(item); RegisterItem(item);
} }
@@ -2528,54 +2161,7 @@ namespace Emby.Server.Implementations.Library
item.DateLastSaved = DateTime.UtcNow; item.DateLastSaved = DateTime.UtcNow;
} }
// Resolve and add any local alternate version items that don't exist yet _itemRepository.SaveItems(items, cancellationToken);
// This ensures they exist in the database when LinkedChildren are processed
var allItems = new List<BaseItem>(items);
var parentFolder = parent as Folder;
var parentCollectionType = GetTopFolderContentType(parent);
foreach (var item in items)
{
if (item is Video video && video.LocalAlternateVersions.Length > 0)
{
var videoType = video.GetType();
foreach (var path in video.LocalAlternateVersions)
{
if (string.IsNullOrEmpty(path))
{
continue;
}
// Use the primary video's type for ID calculation to ensure consistency
var altId = GetNewItemId(path, videoType);
if (GetItemById(altId) is null && !allItems.Any(i => i.Id.Equals(altId)))
{
// Alternate version doesn't exist, resolve and create it
// ensuring it has the same type as the primary video
var altVideo = ResolveAlternateVersion(path, videoType, parentFolder, parentCollectionType);
if (altVideo is not null)
{
altVideo.OwnerId = video.Id;
altVideo.SetPrimaryVersionId(video.Id);
// ResolveAlternateVersion only sees the alternate's primary file.
// If the alternate is itself a stack (e.g. 1080p part1 + part2),
// detect its parts from sibling files so its AdditionalParts persist.
SetAdditionalPartsFromStack(altVideo, path);
allItems.Add(altVideo);
}
}
}
}
}
_persistenceService.SaveItems(allItems, cancellationToken);
foreach (var item in allItems)
{
if (!items.Contains(item))
{
RegisterItem(item);
}
}
if (parent is Folder folder) if (parent is Folder folder)
{ {
@@ -2619,7 +2205,7 @@ namespace Emby.Server.Implementations.Library
/// <inheritdoc /> /// <inheritdoc />
public async Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken) public async Task ReattachUserDataAsync(BaseItem item, CancellationToken cancellationToken)
{ {
await _persistenceService.ReattachUserDataAsync(item, cancellationToken).ConfigureAwait(false); await _itemRepository.ReattachUserDataAsync(item, cancellationToken).ConfigureAwait(false);
} }
public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason) public async Task RunMetadataSavers(BaseItem item, ItemUpdateType updateReason)
@@ -2703,7 +2289,7 @@ namespace Emby.Server.Implementations.Library
if (item is null) if (item is null)
{ {
return []; return new List<Folder>();
} }
return GetCollectionFoldersInternal(item, allUserRootChildren); return GetCollectionFoldersInternal(item, allUserRootChildren);
@@ -3247,9 +2833,8 @@ namespace Emby.Server.Implementations.Library
public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService) public IEnumerable<BaseItem> FindExtras(BaseItem owner, IReadOnlyList<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
{ {
// Apply .ignore rules // Apply .ignore rules
var filtered = fileSystemChildren.Where(c => !_dotIgnoreIgnoreRule.ShouldIgnore(c, owner)).ToList(); var filtered = fileSystemChildren.Where(c => !DotIgnoreIgnoreRule.IsIgnored(c, owner)).ToList();
var isFolder = owner.IsFolder || (owner is Video video && (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd)); var ownerVideoInfo = VideoResolver.Resolve(owner.Path, owner.IsFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath);
var ownerVideoInfo = VideoResolver.Resolve(owner.Path, isFolder, _namingOptions, libraryRoot: owner.ContainingFolderPath);
if (ownerVideoInfo is null) if (ownerVideoInfo is null)
{ {
yield break; yield break;
@@ -3311,16 +2896,10 @@ namespace Emby.Server.Implementations.Library
extra.ExtraType = extraType; extra.ExtraType = extraType;
} }
// Only return items that are actual extras (have ExtraType set) extra.ParentId = Guid.Empty;
// Note: OwnerId and ParentId are set by RefreshExtras, not here, extra.OwnerId = owner.Id;
// so that RefreshExtras can detect when they need updating and set ForceSave. extra.IsInMixedFolder = isInMixedFolder;
if (extra.ExtraType is not null) return extra;
{
extra.IsInMixedFolder = isInMixedFolder;
return extra;
}
return null;
} }
} }
@@ -3339,7 +2918,7 @@ namespace Emby.Server.Implementations.Library
public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query) public IReadOnlyList<PersonInfo> GetPeople(InternalPeopleQuery query)
{ {
return _peopleRepository.GetPeople(query).Items; return _peopleRepository.GetPeople(query);
} }
public IReadOnlyList<PersonInfo> GetPeople(BaseItem item) public IReadOnlyList<PersonInfo> GetPeople(BaseItem item)
@@ -3360,33 +2939,24 @@ namespace Emby.Server.Implementations.Library
return []; return [];
} }
public QueryResult<BaseItem> GetPeopleItems(InternalPeopleQuery query) public IReadOnlyList<Person> GetPeopleItems(InternalPeopleQuery query)
{ {
var queryResult = _peopleRepository.GetPeople(query); return _peopleRepository.GetPeopleNames(query)
var baseItems = queryResult.Items.Select(i => .Select(i =>
{
try
{
return GetPerson(i.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "error retrieving BaseItem for person: {0}", i.Name);
return null;
}
})
.Where(i => i is not null)
.Where(i => query.User is null || i!.IsVisible(query.User))
.OfType<BaseItem>()
.ToList()
.AsReadOnly();
return new QueryResult<BaseItem>
{ {
StartIndex = queryResult.StartIndex, try
TotalRecordCount = queryResult.TotalRecordCount, {
Items = baseItems, return GetPerson(i);
}; }
catch (Exception ex)
{
_logger.LogError(ex, "Error getting person");
return null;
}
})
.Where(i => i is not null)
.Where(i => query.User is null || i!.IsVisible(query.User))
.ToList()!; // null values are filtered out
} }
public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query) public IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query)
@@ -3815,46 +3385,5 @@ namespace Emby.Server.Implementations.Library
_fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path)); _fileSystem.CreateShortcut(lnk, _appHost.ReverseVirtualPath(path));
RemoveContentTypeOverrides(path); RemoveContentTypeOverrides(path);
} }
/// <inheritdoc />
public async Task RerouteLinkedChildReferencesAsync(Guid fromChildId, Guid toChildId)
{
var affectedParentIds = _linkedChildrenService.RerouteLinkedChildren(fromChildId, toChildId);
// Update in-memory LinkedChildren and re-save metadata (NFO) for affected parents
foreach (var parentId in affectedParentIds)
{
if (GetItemById(parentId) is Folder parent)
{
foreach (var lc in parent.LinkedChildren)
{
if (lc.ItemId.HasValue && lc.ItemId.Value.Equals(fromChildId))
{
lc.ItemId = toChildId;
}
}
await RunMetadataSavers(parent, ItemUpdateType.MetadataEdit).ConfigureAwait(false);
}
}
}
/// <inheritdoc />
public QueryFiltersLegacy GetQueryFiltersLegacy(InternalItemsQuery query)
{
if (query.User is not null)
{
AddUserToQuery(query, query.User);
}
SetTopParentOrAncestorIds(query);
return _itemRepository.GetQueryFiltersLegacy(query);
}
/// <inheritdoc />
public IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType)
{
return _mediaStreamRepository.GetMediaStreamLanguages(mediaStreamType);
}
} }
} }

View File

@@ -23,7 +23,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@@ -424,7 +423,7 @@ namespace Emby.Server.Implementations.Library
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage); MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs, user.SubtitleMode, audioLanguage);
} }
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection, string originalLanguage) private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{ {
if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection) if (userData is not null && userData.AudioStreamIndex.HasValue && user.RememberAudioSelections && allowRememberingSelection)
{ {
@@ -438,42 +437,7 @@ namespace Emby.Server.Implementations.Library
} }
} }
if (string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase)) var preferredAudio = NormalizeLanguage(user.AudioLanguagePreference);
{
originalLanguage = !string.IsNullOrWhiteSpace(originalLanguage)
? originalLanguage.Split(',').FirstOrDefault()
: null;
if (user.PlayDefaultAudioTrack)
{
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(
source.MediaStreams,
NormalizeLanguage(originalLanguage),
user.PlayDefaultAudioTrack);
return;
}
var originalIndex = source.MediaStreams.FindIndex(i => i.Type == MediaStreamType.Audio && i.IsOriginal);
if (!string.IsNullOrWhiteSpace(originalLanguage) && originalIndex != -1)
{
var mediaLanguageOriginal = source.MediaStreams[originalIndex].Language;
if (NormalizeLanguage(mediaLanguageOriginal).Contains(NormalizeLanguage(originalLanguage).FirstOrDefault()))
{
source.DefaultAudioStreamIndex = originalIndex;
return;
}
}
else if (originalIndex != -1)
{
source.DefaultAudioStreamIndex = originalIndex;
return;
}
}
var preferredAudio = string.Equals(user.AudioLanguagePreference, "OriginalLanguage", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(originalLanguage)
? NormalizeLanguage(originalLanguage)
: NormalizeLanguage(user.AudioLanguagePreference);
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack); source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.PlayDefaultAudioTrack);
if (user.PlayDefaultAudioTrack) if (user.PlayDefaultAudioTrack)
@@ -498,19 +462,7 @@ namespace Emby.Server.Implementations.Library
var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections; var allowRememberingSelection = item is null || item.EnableRememberingTrackSelections;
var originalLanguage = item?.OriginalLanguage ?? item switch SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
{
Episode episode => episode.Series.OriginalLanguage,
Video video => video.GetOwner() switch
{
Episode ownerEpisode => ownerEpisode.OriginalLanguage ?? ownerEpisode.Series.OriginalLanguage,
BaseItem owner => owner.OriginalLanguage,
null => null
},
_ => null
};
SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection, originalLanguage);
SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection); SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
} }
else if (mediaType == MediaType.Audio) else if (mediaType == MediaType.Audio)

View File

@@ -37,25 +37,15 @@ namespace Emby.Server.Implementations.Library
while (attributeIndex > -1 && attributeIndex < maxIndex) while (attributeIndex > -1 && attributeIndex < maxIndex)
{ {
var attributeEnd = attributeIndex + attribute.Length; var attributeEnd = attributeIndex + attribute.Length;
if (attributeIndex > 0) if (attributeIndex > 0
&& str[attributeIndex - 1] == '['
&& (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{ {
var attributeOpener = str[attributeIndex - 1]; var closingIndex = str[attributeEnd..].IndexOf(']');
var attributeCloser = attributeOpener switch // Must be at least 1 character before the closing bracket.
if (closingIndex > 1)
{ {
'[' => ']', return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
'(' => ')',
'{' => '}',
_ => '\0'
};
if (attributeCloser != '\0' && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{
var closingIndex = str[attributeEnd..].IndexOf(attributeCloser);
// Must be at least 1 character before the closing bracket.
if (closingIndex > 1)
{
return str[(attributeEnd + 1)..(attributeEnd + closingIndex)].Trim().ToString();
}
} }
} }
@@ -70,16 +60,6 @@ namespace Emby.Server.Implementations.Library
return match ? imdbId.ToString() : null; return match ? imdbId.ToString() : null;
} }
// Allow tmdb as an alias for tmdbid
if (attribute.Equals("tmdbid", StringComparison.OrdinalIgnoreCase))
{
var tmdbValue = str.GetAttributeValue("tmdb");
if (tmdbValue is not null)
{
return tmdbValue;
}
}
return null; return null;
} }

View File

@@ -6,7 +6,6 @@ using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library; namespace Emby.Server.Implementations.Library;
@@ -15,22 +14,18 @@ namespace Emby.Server.Implementations.Library;
/// </summary> /// </summary>
public class PathManager : IPathManager public class PathManager : IPathManager
{ {
private readonly ILogger<PathManager> _logger;
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PathManager"/> class. /// Initializes a new instance of the <see cref="PathManager"/> class.
/// </summary> /// </summary>
/// <param name="logger">The logger.</param>
/// <param name="config">The server configuration manager.</param> /// <param name="config">The server configuration manager.</param>
/// <param name="appPaths">The application paths.</param> /// <param name="appPaths">The application paths.</param>
public PathManager( public PathManager(
ILogger<PathManager> logger,
IServerConfigurationManager config, IServerConfigurationManager config,
IApplicationPaths appPaths) IApplicationPaths appPaths)
{ {
_logger = logger;
_config = config; _config = config;
_appPaths = appPaths; _appPaths = appPaths;
} }
@@ -40,43 +35,31 @@ public class PathManager : IPathManager
private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments"); private string AttachmentCachePath => Path.Combine(_appPaths.DataPath, "attachments");
/// <inheritdoc /> /// <inheritdoc />
public string? GetAttachmentPath(string mediaSourceId, string fileName) public string GetAttachmentPath(string mediaSourceId, string fileName)
{ {
var folder = GetAttachmentFolderPath(mediaSourceId); return Path.Combine(GetAttachmentFolderPath(mediaSourceId), fileName);
return folder is null ? null : Path.Combine(folder, fileName);
} }
/// <inheritdoc /> /// <inheritdoc />
public string? GetAttachmentFolderPath(string mediaSourceId) public string GetAttachmentFolderPath(string mediaSourceId)
{ {
if (!Guid.TryParse(mediaSourceId, out var parsed)) var id = Guid.Parse(mediaSourceId).ToString("D", CultureInfo.InvariantCulture).AsSpan();
{
_logger.LogDebug("MediaSource Id '{MediaSourceId}' is not a GUID; no on-disk attachment folder.", mediaSourceId);
return null;
}
var id = parsed.ToString("D", CultureInfo.InvariantCulture).AsSpan();
return Path.Join(AttachmentCachePath, id[..2], id); return Path.Join(AttachmentCachePath, id[..2], id);
} }
/// <inheritdoc /> /// <inheritdoc />
public string? GetSubtitleFolderPath(string mediaSourceId) public string GetSubtitleFolderPath(string mediaSourceId)
{ {
if (!Guid.TryParse(mediaSourceId, out var parsed)) var id = Guid.Parse(mediaSourceId).ToString("D", CultureInfo.InvariantCulture).AsSpan();
{
_logger.LogDebug("MediaSource Id '{MediaSourceId}' is not a GUID; no on-disk subtitle folder.", mediaSourceId);
return null;
}
var id = parsed.ToString("D", CultureInfo.InvariantCulture).AsSpan();
return Path.Join(SubtitleCachePath, id[..2], id); return Path.Join(SubtitleCachePath, id[..2], id);
} }
/// <inheritdoc /> /// <inheritdoc />
public string? GetSubtitlePath(string mediaSourceId, int streamIndex, string extension) public string GetSubtitlePath(string mediaSourceId, int streamIndex, string extension)
{ {
var folder = GetSubtitleFolderPath(mediaSourceId); return Path.Combine(GetSubtitleFolderPath(mediaSourceId), streamIndex.ToString(CultureInfo.InvariantCulture) + extension);
return folder is null ? null : Path.Combine(folder, streamIndex.ToString(CultureInfo.InvariantCulture) + extension);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -107,23 +90,12 @@ public class PathManager : IPathManager
public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item) public IReadOnlyList<string> GetExtractedDataPaths(BaseItem item)
{ {
var mediaSourceId = item.Id.ToString("N", CultureInfo.InvariantCulture); var mediaSourceId = item.Id.ToString("N", CultureInfo.InvariantCulture);
List<string> paths = []; return [
var attachmentFolder = GetAttachmentFolderPath(mediaSourceId); GetAttachmentFolderPath(mediaSourceId),
if (attachmentFolder is not null) GetSubtitleFolderPath(mediaSourceId),
{ GetTrickplayDirectory(item, false),
paths.Add(attachmentFolder); GetTrickplayDirectory(item, true),
} GetChapterImageFolderPath(item)
];
var subtitleFolder = GetSubtitleFolderPath(mediaSourceId);
if (subtitleFolder is not null)
{
paths.Add(subtitleFolder);
}
paths.Add(GetTrickplayDirectory(item, false));
paths.Add(GetTrickplayDirectory(item, true));
paths.Add(GetChapterImageFolderPath(item));
return paths;
} }
} }

View File

@@ -28,16 +28,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver public partial class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{ {
private readonly IImageProcessor _imageProcessor; private readonly IImageProcessor _imageProcessor;
private readonly VideoListResolver _videoListResolver;
private static readonly CollectionType[] _validCollectionTypes = private static readonly CollectionType[] _validCollectionTypes = new[]
[ {
CollectionType.movies, CollectionType.movies,
CollectionType.homevideos, CollectionType.homevideos,
CollectionType.musicvideos, CollectionType.musicvideos,
CollectionType.tvshows, CollectionType.tvshows,
CollectionType.photos CollectionType.photos
]; };
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MovieResolver"/> class. /// Initializes a new instance of the <see cref="MovieResolver"/> class.
@@ -46,12 +45,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
/// <param name="logger">The logger.</param> /// <param name="logger">The logger.</param>
/// <param name="namingOptions">The naming options.</param> /// <param name="namingOptions">The naming options.</param>
/// <param name="directoryService">The directory service.</param> /// <param name="directoryService">The directory service.</param>
/// <param name="videoListResolver">The video list resolver.</param> public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService)
public MovieResolver(IImageProcessor imageProcessor, ILogger<MovieResolver> logger, NamingOptions namingOptions, IDirectoryService directoryService, VideoListResolver videoListResolver)
: base(logger, namingOptions, directoryService) : base(logger, namingOptions, directoryService)
{ {
_imageProcessor = imageProcessor; _imageProcessor = imageProcessor;
_videoListResolver = videoListResolver;
} }
/// <summary> /// <summary>
@@ -231,7 +228,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (collectionType == CollectionType.tvshows) if (collectionType == CollectionType.tvshows)
{ {
return ResolveVideos<Episode>(parent, files, true, collectionType, true); return ResolveVideos<Episode>(parent, files, false, collectionType, true);
} }
return null; return null;
@@ -277,7 +274,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
.Where(f => f is not null) .Where(f => f is not null)
.ToList(); .ToList();
var resolverResult = _videoListResolver.Resolve(videoInfos, supportMultiEditions, parseName, parent.ContainingFolderPath, collectionType); var resolverResult = VideoListResolver.Resolve(videoInfos, NamingOptions, supportMultiEditions, parseName, parent.ContainingFolderPath);
var result = new MultiItemResolverResult var result = new MultiItemResolverResult
{ {
@@ -305,7 +302,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
ProductionYear = video.Year, ProductionYear = video.Year,
Name = parseName ? video.Name : firstVideo.Name, Name = parseName ? video.Name : firstVideo.Name,
AdditionalParts = additionalParts, AdditionalParts = additionalParts,
LocalAlternateVersions = video.AlternateVersions.Select(av => av.Files[0].Path).ToArray() LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToArray()
}; };
SetVideoType(videoItem, firstVideo); SetVideoType(videoItem, firstVideo);
@@ -334,13 +331,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
for (var j = 0; j < current.AlternateVersions.Count; j++) for (var j = 0; j < current.AlternateVersions.Count; j++)
{ {
var alternate = current.AlternateVersions[j]; if (ContainsFile(current.AlternateVersions[j], file))
for (var k = 0; k < alternate.Files.Count; k++)
{ {
if (ContainsFile(alternate.Files[k], file)) return true;
{
return true;
}
} }
} }
} }

View File

@@ -1,10 +1,8 @@
#nullable disable #nullable disable
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Server.Implementations.Library;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@@ -83,34 +81,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
episode.ParentIndexNumber = 1; episode.ParentIndexNumber = 1;
} }
SetProviderIdFromPath(episode, args.Path);
return episode; return episode;
} }
return null; return null;
} }
/// <summary>
/// Sets provider ids from the episode file name.
/// </summary>
/// <param name="item">The episode.</param>
/// <param name="path">The episode file path.</param>
private static void SetProviderIdFromPath(Episode item, string path)
{
var justName = Path.GetFileNameWithoutExtension(path.AsSpan());
var imdbId = justName.GetAttributeValue("imdbid");
item.TrySetProviderId(MetadataProvider.Imdb, imdbId);
var tvdbId = justName.GetAttributeValue("tvdbid");
item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId);
var tvmazeId = justName.GetAttributeValue("tvmazeid");
item.TrySetProviderId(MetadataProvider.TvMaze, tvmazeId);
var tmdbId = justName.GetAttributeValue("tmdbid");
item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId);
}
} }
} }

View File

@@ -1,15 +1,10 @@
#nullable disable #nullable disable
using System;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using Emby.Naming.TV; using Emby.Naming.TV;
using Emby.Server.Implementations.Library;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -82,14 +77,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
return null; return null;
} }
var hasAnyVideo = Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
.Any(file => _namingOptions.VideoFileExtensions.Contains(Path.GetExtension(file)));
if (!hasAnyVideo)
{
return null;
}
} }
if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name)) if (season.IndexNumber.HasValue && string.IsNullOrEmpty(season.Name))
@@ -104,31 +91,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
args.LibraryOptions.PreferredMetadataLanguage); args.LibraryOptions.PreferredMetadataLanguage);
} }
SetProviderIdFromPath(season, path);
return season; return season;
} }
return null; return null;
} }
/// <summary>
/// Sets provider ids from the season folder name.
/// </summary>
/// <param name="item">The season.</param>
/// <param name="path">The season folder path.</param>
private static void SetProviderIdFromPath(Season item, string path)
{
var justName = Path.GetFileName(path.AsSpan());
var tvdbId = justName.GetAttributeValue("tvdbid");
item.TrySetProviderId(MetadataProvider.Tvdb, tvdbId);
var tvmazeId = justName.GetAttributeValue("tvmazeid");
item.TrySetProviderId(MetadataProvider.TvMaze, tvmazeId);
var tmdbId = justName.GetAttributeValue("tmdbid");
item.TrySetProviderId(MetadataProvider.Tmdb, tmdbId);
}
} }
} }

View File

@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for audio tracks.
/// </summary>
public class AudioSimilarItemsProvider : ILocalSimilarItemsProvider<Audio>
{
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="AudioSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public AudioSimilarItemsProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(Audio item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
ExcludeArtistIds = [.. query.ExcludeArtistIds],
IncludeItemTypes = [BaseItemKind.Audio],
EnableGroupByMetadataKey = false,
EnableTotalRecordCount = true,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return Task.FromResult(_libraryManager.GetItemList(internalQuery));
}
}

View File

@@ -1,94 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for Live TV programs.
/// </summary>
public class LiveTvProgramSimilarItemsProvider : ILocalSimilarItemsProvider<LiveTvProgram>
{
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary>
/// Initializes a new instance of the <see cref="LiveTvProgramSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="serverConfigurationManager">The server configuration manager.</param>
public LiveTvProgramSimilarItemsProvider(
ILibraryManager libraryManager,
IServerConfigurationManager serverConfigurationManager)
{
_libraryManager = libraryManager;
_serverConfigurationManager = serverConfigurationManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(LiveTvProgram item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
BaseItemKind[] includeItemTypes;
bool enableGroupByMetadataKey;
bool enableTotalRecordCount;
if (item.IsMovie)
{
// Movie-like program
var itemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
{
itemTypes.Add(BaseItemKind.Trailer);
itemTypes.Add(BaseItemKind.LiveTvProgram);
}
includeItemTypes = [.. itemTypes];
enableGroupByMetadataKey = true;
enableTotalRecordCount = false;
}
else if (item.IsSeries)
{
// Series-like program
includeItemTypes = [BaseItemKind.Series];
enableGroupByMetadataKey = false;
enableTotalRecordCount = true;
}
else
{
// Default - match same type
includeItemTypes = [item.GetBaseItemKind()];
enableGroupByMetadataKey = false;
enableTotalRecordCount = true;
}
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
IncludeItemTypes = includeItemTypes,
EnableGroupByMetadataKey = enableGroupByMetadataKey,
EnableTotalRecordCount = enableTotalRecordCount,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return Task.FromResult(_libraryManager.GetItemList(internalQuery));
}
}

View File

@@ -1,91 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for movies and trailers.
/// </summary>
public sealed class MovieSimilarItemsProvider : ILocalSimilarItemsProvider<Movie>, ILocalSimilarItemsProvider<Trailer>
{
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _serverConfigurationManager;
/// <summary>
/// Initializes a new instance of the <see cref="MovieSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="serverConfigurationManager">The server configuration manager.</param>
public MovieSimilarItemsProvider(
ILibraryManager libraryManager,
IServerConfigurationManager serverConfigurationManager)
{
_libraryManager = libraryManager;
_serverConfigurationManager = serverConfigurationManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(Movie item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
return Task.FromResult(GetSimilarMovieItems(item, query));
}
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(Trailer item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
return Task.FromResult(GetSimilarMovieItems(item, query));
}
bool ILocalSimilarItemsProvider.Supports(Type itemType)
=> typeof(Movie).IsAssignableFrom(itemType) || typeof(Trailer).IsAssignableFrom(itemType);
Task<IReadOnlyList<BaseItem>> ILocalSimilarItemsProvider.GetSimilarItemsAsync(BaseItem item, SimilarItemsQuery query, CancellationToken cancellationToken)
=> item switch
{
Movie movie => GetSimilarItemsAsync(movie, query, cancellationToken),
Trailer trailer => GetSimilarItemsAsync(trailer, query, cancellationToken),
_ => throw new ArgumentException($"Unsupported item type {item.GetType()}", nameof(item))
};
private IReadOnlyList<BaseItem> GetSimilarMovieItems(BaseItem item, SimilarItemsQuery query)
{
var includeItemTypes = new List<BaseItemKind> { BaseItemKind.Movie };
if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions)
{
includeItemTypes.Add(BaseItemKind.Trailer);
includeItemTypes.Add(BaseItemKind.LiveTvProgram);
}
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
IncludeItemTypes = [.. includeItemTypes],
EnableGroupByMetadataKey = true,
EnableTotalRecordCount = false,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return _libraryManager.GetItemList(internalQuery);
}
}

View File

@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for music albums.
/// </summary>
public class MusicAlbumSimilarItemsProvider : ILocalSimilarItemsProvider<MusicAlbum>
{
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="MusicAlbumSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public MusicAlbumSimilarItemsProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(MusicAlbum item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
ExcludeArtistIds = [.. query.ExcludeArtistIds],
IncludeItemTypes = [BaseItemKind.MusicAlbum],
EnableGroupByMetadataKey = false,
EnableTotalRecordCount = true,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return Task.FromResult(_libraryManager.GetItemList(internalQuery));
}
}

View File

@@ -1,55 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for music artists.
/// </summary>
public class MusicArtistSimilarItemsProvider : ILocalSimilarItemsProvider<MusicArtist>
{
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="MusicArtistSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public MusicArtistSimilarItemsProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(MusicArtist item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
ExcludeArtistIds = [.. query.ExcludeArtistIds],
IncludeItemTypes = [BaseItemKind.MusicArtist],
EnableGroupByMetadataKey = false,
EnableTotalRecordCount = true,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return Task.FromResult(_libraryManager.GetItemList(internalQuery));
}
}

View File

@@ -1,54 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Provides similar items for TV series.
/// </summary>
public class SeriesSimilarItemsProvider : ILocalSimilarItemsProvider<Series>
{
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="SeriesSimilarItemsProvider"/> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public SeriesSimilarItemsProvider(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <inheritdoc/>
public string Name => "Local Genre/Tag";
/// <inheritdoc/>
public MetadataPluginType Type => MetadataPluginType.LocalSimilarityProvider;
/// <inheritdoc/>
public Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(Series item, SimilarItemsQuery query, CancellationToken cancellationToken)
{
var internalQuery = new InternalItemsQuery(query.User)
{
Genres = item.Genres,
Tags = item.Tags,
Limit = query.Limit,
DtoOptions = query.DtoOptions ?? new DtoOptions(),
ExcludeItemIds = [.. query.ExcludeItemIds],
IncludeItemTypes = [BaseItemKind.Series],
EnableGroupByMetadataKey = false,
EnableTotalRecordCount = true,
OrderBy = [(ItemSortBy.Random, SortOrder.Ascending)]
};
return Task.FromResult(_libraryManager.GetItemList(internalQuery));
}
}

View File

@@ -1,406 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using Jellyfin.Database.Implementations.Entities;
using Jellyfin.Extensions.Json;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Library.SimilarItems;
/// <summary>
/// Manages similar items providers and orchestrates similar items operations.
/// </summary>
public class SimilarItemsManager : ISimilarItemsManager
{
private readonly ILogger<SimilarItemsManager> _logger;
private readonly IServerApplicationPaths _appPaths;
private readonly ILibraryManager _libraryManager;
private readonly IFileSystem _fileSystem;
private ISimilarItemsProvider[] _similarItemsProviders = [];
/// <summary>
/// Initializes a new instance of the <see cref="SimilarItemsManager"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="appPaths">The server application paths.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="fileSystem">The file system.</param>
public SimilarItemsManager(
ILogger<SimilarItemsManager> logger,
IServerApplicationPaths appPaths,
ILibraryManager libraryManager,
IFileSystem fileSystem)
{
_logger = logger;
_appPaths = appPaths;
_libraryManager = libraryManager;
_fileSystem = fileSystem;
}
/// <inheritdoc/>
public void AddParts(IEnumerable<ISimilarItemsProvider> providers)
{
_similarItemsProviders = providers.ToArray();
}
/// <inheritdoc/>
public IReadOnlyList<ISimilarItemsProvider> GetSimilarItemsProviders<T>()
where T : BaseItem
{
var itemType = typeof(T);
return _similarItemsProviders
.Where(p => (p is ILocalSimilarItemsProvider local && local.Supports(itemType))
|| (p is IRemoteSimilarItemsProvider remote && remote.Supports(itemType)))
.ToList();
}
/// <inheritdoc/>
public async Task<IReadOnlyList<BaseItem>> GetSimilarItemsAsync(
BaseItem item,
IReadOnlyList<Guid> excludeArtistIds,
User? user,
DtoOptions dtoOptions,
int? limit,
LibraryOptions? libraryOptions,
CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(item);
ArgumentNullException.ThrowIfNull(excludeArtistIds);
var itemType = item.GetType();
var requestedLimit = limit ?? 50;
var itemKind = item.GetBaseItemKind();
// Ensure ProviderIds is included in DtoOptions for matching remote provider responses
if (!dtoOptions.Fields.Contains(ItemFields.ProviderIds))
{
dtoOptions.Fields = dtoOptions.Fields.Concat([ItemFields.ProviderIds]).ToArray();
}
// Local providers are always enabled. Remote providers must be explicitly enabled.
var localProviders = _similarItemsProviders
.OfType<ILocalSimilarItemsProvider>()
.Where(p => p.Supports(itemType))
.ToList();
var remoteProviders = _similarItemsProviders
.OfType<IRemoteSimilarItemsProvider>()
.Where(p => p.Supports(itemType));
var matchingProviders = new List<ISimilarItemsProvider>(localProviders);
var typeOptions = libraryOptions?.GetTypeOptions(itemType.Name);
if (typeOptions?.SimilarItemProviders?.Length > 0)
{
matchingProviders.AddRange(remoteProviders
.Where(p => typeOptions.SimilarItemProviders.Contains(p.Name, StringComparer.OrdinalIgnoreCase)));
}
var orderConfig = typeOptions?.SimilarItemProviderOrder is { Length: > 0 } order
? order
: typeOptions?.SimilarItemProviders;
var orderedProviders = matchingProviders
.OrderBy(p => GetConfiguredSimilarProviderOrder(orderConfig, p.Name))
.ToList();
var allResults = new List<(BaseItem Item, float Score)>();
var excludeIds = new HashSet<Guid> { item.Id };
foreach (var (providerOrder, provider) in orderedProviders.Index())
{
if (allResults.Count >= requestedLimit || cancellationToken.IsCancellationRequested)
{
break;
}
try
{
if (provider is ILocalSimilarItemsProvider localProvider)
{
var query = new SimilarItemsQuery
{
User = user,
Limit = requestedLimit - allResults.Count,
DtoOptions = dtoOptions,
ExcludeItemIds = [.. excludeIds],
ExcludeArtistIds = excludeArtistIds
};
var items = await localProvider.GetSimilarItemsAsync(item, query, cancellationToken).ConfigureAwait(false);
foreach (var (position, resultItem) in items.Index())
{
if (excludeIds.Add(resultItem.Id))
{
var score = CalculateScore(null, providerOrder, position);
allResults.Add((resultItem, score));
}
}
}
else if (provider is IRemoteSimilarItemsProvider remoteProvider)
{
var cachePath = GetSimilarItemsCachePath(provider.Name, itemType.Name, item.Id);
var cachedReferences = await TryReadSimilarItemsCacheAsync(cachePath, cancellationToken).ConfigureAwait(false);
if (cachedReferences is not null)
{
var resolvedItems = ResolveRemoteReferences(cachedReferences, providerOrder, user, dtoOptions, itemKind, excludeIds);
allResults.AddRange(resolvedItems);
continue;
}
var query = new SimilarItemsQuery
{
User = user,
Limit = requestedLimit - allResults.Count,
DtoOptions = dtoOptions,
ExcludeItemIds = [.. excludeIds],
ExcludeArtistIds = excludeArtistIds
};
// Collect references in batches and resolve against local library.
// Stop fetching once we have enough resolved local items.
const int BatchSize = 20;
var remaining = requestedLimit - allResults.Count;
var collectedReferences = new List<SimilarItemReference>();
var pendingBatch = new List<SimilarItemReference>();
await foreach (var reference in remoteProvider.GetSimilarItemsAsync(item, query, cancellationToken).ConfigureAwait(false))
{
collectedReferences.Add(reference);
pendingBatch.Add(reference);
if (pendingBatch.Count >= BatchSize)
{
var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds);
allResults.AddRange(resolvedItems);
remaining -= resolvedItems.Count;
pendingBatch.Clear();
if (remaining <= 0)
{
break;
}
}
}
// Resolve any remaining references in the last partial batch
if (pendingBatch.Count > 0)
{
var resolvedItems = ResolveRemoteReferences(pendingBatch, providerOrder, user, dtoOptions, itemKind, excludeIds);
allResults.AddRange(resolvedItems);
}
if (collectedReferences.Count > 0 && provider.CacheDuration is not null)
{
await SaveSimilarItemsCacheAsync(cachePath, collectedReferences, provider.CacheDuration.Value, cancellationToken).ConfigureAwait(false);
}
}
}
catch (OperationCanceledException)
{
break;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Similar items provider {ProviderName} failed for item {ItemId}", provider.Name, item.Id);
}
}
return allResults
.OrderByDescending(x => x.Score)
.Select(x => x.Item)
.Take(requestedLimit)
.ToList();
}
private List<(BaseItem Item, float Score)> ResolveRemoteReferences(
IReadOnlyList<SimilarItemReference> references,
int providerOrder,
User? user,
DtoOptions dtoOptions,
BaseItemKind itemKind,
HashSet<Guid> excludeIds)
{
if (references.Count == 0)
{
return [];
}
var resolvedById = new Dictionary<Guid, (BaseItem Item, float Score)>();
var providerLookup = new Dictionary<(string ProviderName, string ProviderId), (float? Score, int Position)>(StringTupleComparer.Instance);
foreach (var (position, match) in references.Index())
{
var lookupKey = (match.ProviderName, match.ProviderId);
if (!providerLookup.TryGetValue(lookupKey, out var existing))
{
providerLookup[lookupKey] = (match.Score, position);
}
else if (match.Score > existing.Score || (match.Score == existing.Score && position < existing.Position))
{
providerLookup[lookupKey] = (match.Score, position);
}
}
var allProviderIds = providerLookup
.GroupBy(kvp => kvp.Key.ProviderName)
.ToDictionary(g => g.Key, g => g.Select(x => x.Key.ProviderId).ToArray());
var query = new InternalItemsQuery(user)
{
HasAnyProviderIds = allProviderIds,
IncludeItemTypes = [itemKind],
DtoOptions = dtoOptions
};
var items = _libraryManager.GetItemList(query);
foreach (var item in items)
{
if (excludeIds.Contains(item.Id) || resolvedById.ContainsKey(item.Id))
{
continue;
}
foreach (var providerName in allProviderIds.Keys)
{
if (item.TryGetProviderId(providerName, out var itemProviderId) && providerLookup.TryGetValue((providerName, itemProviderId), out var matchInfo))
{
var score = CalculateScore(matchInfo.Score, providerOrder, matchInfo.Position);
if (!resolvedById.TryGetValue(item.Id, out var existing) || existing.Score < score)
{
excludeIds.Add(item.Id);
resolvedById[item.Id] = (item, score);
}
break;
}
}
}
return [.. resolvedById.Values];
}
private static float CalculateScore(float? matchScore, int providerOrder, int position)
{
// Use provider-supplied score if available, otherwise derive from position
var baseScore = matchScore ?? (1.0f - (position * 0.02f));
// Apply small boost based on provider order (higher priority providers get small bonus)
var priorityBoost = Math.Max(0, 10 - providerOrder) * 0.005f;
return Math.Clamp(baseScore + priorityBoost, 0f, 1f);
}
private static int GetConfiguredSimilarProviderOrder(string[]? orderConfig, string providerName)
{
if (orderConfig is null || orderConfig.Length == 0)
{
return int.MaxValue;
}
var index = Array.FindIndex(orderConfig, name => string.Equals(name, providerName, StringComparison.OrdinalIgnoreCase));
return index >= 0 ? index : int.MaxValue;
}
private string GetSimilarItemsCachePath(string providerName, string baseItemType, Guid itemId)
{
var dataPath = Path.Combine(
_appPaths.CachePath,
$"{providerName.ToLowerInvariant()}-similar-{baseItemType.ToLowerInvariant()}");
return Path.Combine(dataPath, $"{itemId.ToString("N", CultureInfo.InvariantCulture)}.json");
}
private async Task<List<SimilarItemReference>?> TryReadSimilarItemsCacheAsync(string cachePath, CancellationToken cancellationToken)
{
var fileInfo = _fileSystem.GetFileSystemInfo(cachePath);
if (!fileInfo.Exists || fileInfo.Length == 0)
{
return null;
}
try
{
var stream = File.OpenRead(cachePath);
await using (stream.ConfigureAwait(false))
{
var cache = await JsonSerializer.DeserializeAsync<SimilarItemsCache>(stream, JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
if (cache?.References is not null && DateTime.UtcNow < cache.ExpiresAt)
{
return cache.References;
}
}
}
catch (IOException ex)
{
_logger.LogWarning(ex, "Failed to read similar items cache from {CachePath}", cachePath);
}
catch (JsonException ex)
{
_logger.LogWarning(ex, "Failed to parse similar items cache from {CachePath}", cachePath);
}
return null;
}
private async Task SaveSimilarItemsCacheAsync(string cachePath, List<SimilarItemReference> references, TimeSpan cacheDuration, CancellationToken cancellationToken)
{
try
{
var directory = Path.GetDirectoryName(cachePath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
var cache = new SimilarItemsCache
{
References = references,
ExpiresAt = DateTime.UtcNow.Add(cacheDuration)
};
var stream = File.Create(cachePath);
await using (stream.ConfigureAwait(false))
{
await JsonSerializer.SerializeAsync(stream, cache, JsonDefaults.Options, cancellationToken).ConfigureAwait(false);
}
}
catch (IOException ex)
{
_logger.LogWarning(ex, "Failed to save similar items cache to {CachePath}", cachePath);
}
}
private sealed class SimilarItemsCache
{
public List<SimilarItemReference>? References { get; set; }
public DateTime ExpiresAt { get; set; }
}
private sealed class StringTupleComparer : IEqualityComparer<(string Key, string Value)>
{
public static readonly StringTupleComparer Instance = new();
public bool Equals((string Key, string Value) x, (string Key, string Value) y)
=> string.Equals(x.Key, y.Key, StringComparison.OrdinalIgnoreCase) &&
string.Equals(x.Value, y.Value, StringComparison.OrdinalIgnoreCase);
public int GetHashCode((string Key, string Value) obj)
=> HashCode.Combine(
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Key),
StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Value));
}
}

View File

@@ -80,7 +80,7 @@ public class SplashscreenPostScanTask : ILibraryPostScanTask
ImageTypes = [imageType], ImageTypes = [imageType],
Limit = 30, Limit = 30,
// TODO max parental rating configurable // TODO max parental rating configurable
MaxParentalRating = new(13, null), MaxParentalRating = new(10, null),
OrderBy = OrderBy =
[ [
(ItemSortBy.Random, SortOrder.Ascending) (ItemSortBy.Random, SortOrder.Ascending)

View File

@@ -177,74 +177,53 @@ namespace Emby.Server.Implementations.Library
}; };
} }
/// <inheritdoc /> private UserItemData? GetUserData(User user, Guid itemId, List<string> keys)
public Dictionary<Guid, UserItemData> GetUserDataBatch(IReadOnlyList<BaseItem> items, User user)
{ {
var result = new Dictionary<Guid, UserItemData>(items.Count); var cacheKey = GetCacheKey(user.InternalId, itemId);
var itemsNeedingQuery = new List<(BaseItem Item, List<string> Keys)>();
foreach (var item in items) if (_cache.TryGet(cacheKey, out var data))
{ {
var cacheKey = GetCacheKey(user.InternalId, item.Id); return data;
if (_cache.TryGet(cacheKey, out var cachedData))
{
result[item.Id] = cachedData;
}
else
{
var userData = item.UserData?.Where(e => e.UserId.Equals(user.Id)).Select(Map).FirstOrDefault();
if (userData is not null)
{
result[item.Id] = userData;
_cache.AddOrUpdate(cacheKey, userData);
}
else
{
var keys = item.GetUserDataKeys();
itemsNeedingQuery.Add((item, keys));
}
}
} }
if (itemsNeedingQuery.Count == 0) data = GetUserDataInternal(user.Id, itemId, keys);
{
return result;
}
// Build a single query for all missing items if (data is null)
var allItemIds = itemsNeedingQuery.Select(x => x.Item.Id).ToList();
var allKeys = itemsNeedingQuery.SelectMany(x => x.Keys).Distinct().ToList();
if (allKeys.Count > 0)
{ {
using var context = _repository.CreateDbContext(); return new UserItemData()
var userDataArray = context.UserData
.AsNoTracking()
.Where(e => e.UserId.Equals(user.Id))
.WhereOneOrMany(allItemIds, e => e.ItemId)
.WhereOneOrMany(allKeys, e => e.CustomDataKey)
.ToArray();
var userDataByItem = userDataArray.GroupBy(e => e.ItemId).ToDictionary(g => g.Key, g => g.ToArray());
foreach (var (item, keys) in itemsNeedingQuery)
{ {
UserItemData userData; Key = keys[0],
if (userDataByItem.TryGetValue(item.Id, out var itemUserData) && itemUserData.Length > 0) };
{
var directDataReference = itemUserData.FirstOrDefault(e => e.CustomDataKey == item.Id.ToString("N"));
userData = directDataReference is not null ? Map(directDataReference) : Map(itemUserData.First());
}
else
{
userData = new UserItemData { Key = keys.Count > 0 ? keys[0] : string.Empty };
}
result[item.Id] = userData;
var cacheKey = GetCacheKey(user.InternalId, item.Id);
_cache.AddOrUpdate(cacheKey, userData);
}
} }
return result; return _cache.GetOrAdd(cacheKey, _ => data);
}
private UserItemData? GetUserDataInternal(Guid userId, Guid itemId, List<string> keys)
{
if (keys.Count == 0)
{
return null;
}
using var context = _repository.CreateDbContext();
var userData = context.UserData.AsNoTracking().Where(e => e.ItemId == itemId && keys.Contains(e.CustomDataKey) && e.UserId.Equals(userId)).ToArray();
if (userData.Length > 0)
{
var directDataReference = userData.FirstOrDefault(e => e.CustomDataKey == itemId.ToString("N"));
if (directDataReference is not null)
{
return Map(directDataReference);
}
return Map(userData.First());
}
return new UserItemData
{
Key = keys.Last()!
};
} }
/// <summary> /// <summary>

View File

@@ -59,8 +59,8 @@ namespace Emby.Server.Implementations.Library
var collectionFolder = folder as ICollectionFolder; var collectionFolder = folder as ICollectionFolder;
var folderViewType = collectionFolder?.CollectionType; var folderViewType = collectionFolder?.CollectionType;
// Playlist and BoxSet libraries require special handling because the folder only references linked items // Playlist library requires special handling because the folder only references user playlists
if (folderViewType == CollectionType.playlists || folderViewType == CollectionType.boxsets) if (folderViewType == CollectionType.playlists)
{ {
var items = folder.GetItemList(new InternalItemsQuery(user) var items = folder.GetItemList(new InternalItemsQuery(user)
{ {
@@ -138,7 +138,7 @@ namespace Emby.Server.Implementations.Library
list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList(); list = list.Where(i => !user.GetPreferenceValues<Guid>(PreferenceKind.MyMediaExcludes).Contains(i.Id)).ToList();
} }
var sorted = _libraryManager.Sort(list, user, [ItemSortBy.SortName], SortOrder.Ascending).ToList(); var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews); var orders = user.GetPreferenceValues<Guid>(PreferenceKind.OrderedViews);
return list return list
@@ -205,7 +205,7 @@ namespace Emby.Server.Implementations.Library
var libraryItems = GetItemsForLatestItems(request.User, request, options); var libraryItems = GetItemsForLatestItems(request.User, request, options);
var list = new List<Tuple<BaseItem, List<BaseItem>>>(); var list = new List<Tuple<BaseItem, List<BaseItem>>>();
var containerIndexMap = new Dictionary<Guid, int>();
foreach (var item in libraryItems) foreach (var item in libraryItems)
{ {
// Only grab the index container for media // Only grab the index container for media
@@ -213,16 +213,20 @@ namespace Emby.Server.Implementations.Library
if (container is null) if (container is null)
{ {
list.Add(new Tuple<BaseItem, List<BaseItem>>(null!, new List<BaseItem> { item })); list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item }));
}
else if (containerIndexMap.TryGetValue(container.Id, out var existingIndex))
{
list[existingIndex].Item2.Add(item);
} }
else else
{ {
containerIndexMap[container.Id] = list.Count; var current = list.FirstOrDefault(i => i.Item1 is not null && i.Item1.Id.Equals(container.Id));
list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
if (current is not null)
{
current.Item2.Add(item);
}
else
{
list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
}
} }
if (list.Count >= request.Limit) if (list.Count >= request.Limit)
@@ -251,7 +255,7 @@ namespace Emby.Server.Implementations.Library
return _channelManager.GetLatestChannelItemsInternal( return _channelManager.GetLatestChannelItemsInternal(
new InternalItemsQuery(user) new InternalItemsQuery(user)
{ {
ChannelIds = [parentId], ChannelIds = new[] { parentId },
IsPlayed = request.IsPlayed, IsPlayed = request.IsPlayed,
StartIndex = request.StartIndex, StartIndex = request.StartIndex,
Limit = request.Limit, Limit = request.Limit,
@@ -297,11 +301,11 @@ namespace Emby.Server.Implementations.Library
{ {
if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies)) if (hasCollectionType.All(i => i.CollectionType == CollectionType.movies))
{ {
includeItemTypes = [BaseItemKind.Movie]; includeItemTypes = new[] { BaseItemKind.Movie };
} }
else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows)) else if (hasCollectionType.All(i => i.CollectionType == CollectionType.tvshows))
{ {
includeItemTypes = [BaseItemKind.Episode]; includeItemTypes = new[] { BaseItemKind.Episode };
} }
} }
} }
@@ -340,29 +344,29 @@ namespace Emby.Server.Implementations.Library
} }
var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Length == 0 var excludeItemTypes = includeItemTypes.Length == 0 && mediaTypes.Length == 0
? ? new[]
[ {
BaseItemKind.Person, BaseItemKind.Person,
BaseItemKind.Studio, BaseItemKind.Studio,
BaseItemKind.Year, BaseItemKind.Year,
BaseItemKind.MusicGenre, BaseItemKind.MusicGenre,
BaseItemKind.Genre BaseItemKind.Genre
] }
: Array.Empty<BaseItemKind>(); : Array.Empty<BaseItemKind>();
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
IncludeItemTypes = includeItemTypes, IncludeItemTypes = includeItemTypes,
OrderBy = OrderBy = new[]
[ {
(ItemSortBy.DateCreated, SortOrder.Descending), (ItemSortBy.DateCreated, SortOrder.Descending),
(ItemSortBy.SortName, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Descending),
(ItemSortBy.ProductionYear, SortOrder.Descending) (ItemSortBy.ProductionYear, SortOrder.Descending)
], },
IsFolder = includeItemTypes.Length == 0 ? false : null, IsFolder = includeItemTypes.Length == 0 ? false : null,
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false, IsVirtualItem = false,
Limit = limit * 2, Limit = limit * 5,
IsPlayed = isPlayed, IsPlayed = isPlayed,
DtoOptions = options, DtoOptions = options,
MediaTypes = mediaTypes MediaTypes = mediaTypes
@@ -390,12 +394,6 @@ namespace Emby.Server.Implementations.Library
query.Limit = limit; query.Limit = limit;
return _libraryManager.GetLatestItemList(query, parents, CollectionType.music); return _libraryManager.GetLatestItemList(query, parents, CollectionType.music);
} }
if (collectionType == CollectionType.movies)
{
query.Limit = limit;
return _libraryManager.GetLatestItemList(query, parents, CollectionType.movies);
}
} }
return _libraryManager.GetItemList(query, parents); return _libraryManager.GetItemList(query, parents);

View File

@@ -50,40 +50,21 @@ public class ArtistsValidator
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var names = _itemRepo.GetAllArtistNames(); var names = _itemRepo.GetAllArtistNames();
var existingArtistIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.MusicArtist]
}).ToHashSet();
var existingArtists = _libraryManager.GetArtists(names);
var numComplete = 0; var numComplete = 0;
var count = names.Count; var count = names.Count;
var refreshed = 0;
foreach (var name in names) foreach (var name in names)
{ {
try try
{ {
MusicArtist? item = null; var item = _libraryManager.GetArtist(name);
if (existingArtists.TryGetValue(name, out var artists) && artists.Length > 0)
{
item = artists.OrderBy(i => i.IsAccessedByName ? 1 : 0).First();
}
// Fall back to GetArtist if not found (creates new item if needed) await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
item ??= _libraryManager.GetArtist(name);
var isNew = !existingArtistIds.Contains(item.Id);
var neverRefreshed = item.DateLastRefreshed == default;
if (isNew || neverRefreshed)
{
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
refreshed++;
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
// Don't clutter the log
throw; throw;
} }
catch (Exception ex) catch (Exception ex)
@@ -99,23 +80,30 @@ public class ArtistsValidator
progress.Report(percent); progress.Report(percent);
} }
_logger.LogInformation("Refreshed metadata for {RefreshedCount} new artists out of {TotalCount} total", refreshed, count);
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = [BaseItemKind.MusicArtist], IncludeItemTypes = [BaseItemKind.MusicArtist],
IsDeadArtist = true, IsDeadArtist = true,
IsLocked = false IsLocked = false
}).Cast<MusicArtist>() }).Cast<MusicArtist>().ToList();
.Where(item => item.IsAccessedByName)
.ToList();
foreach (var item in deadEntities) foreach (var item in deadEntities)
{ {
_logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); if (!item.IsAccessedByName)
} {
continue;
}
_libraryManager.DeleteItemsUnsafeFast(deadEntities, deleteSourceFiles: true); _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
_libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
progress.Report(100); progress.Report(100);
} }

View File

@@ -74,7 +74,7 @@ public class CollectionPostScanTask : ILibraryPostScanTask
foreach (var m in movies) foreach (var m in movies)
{ {
if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName) && !movie.PrimaryVersionId.HasValue) if (m is Movie movie && !string.IsNullOrEmpty(movie.CollectionName))
{ {
if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList)) if (collectionNameMoviesMap.TryGetValue(movie.CollectionName, out var movieList))
{ {

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@@ -49,40 +48,17 @@ public class GenresValidator
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var names = _itemRepo.GetGenreNames(); var names = _itemRepo.GetGenreNames();
var existingGenreIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.Genre]
}).ToHashSet();
var existingGenres = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.Genre]
}).Cast<Genre>()
.GroupBy(g => g.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
var numComplete = 0; var numComplete = 0;
var count = names.Count; var count = names.Count;
var refreshed = 0;
foreach (var name in names) foreach (var name in names)
{ {
try try
{ {
Genre? item = null; var item = _libraryManager.GetGenre(name);
if (existingGenres.TryGetValue(name, out var existingGenre))
{
item = existingGenre;
}
// Fall back to GetGenre if not found (creates new item if needed) await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
item ??= _libraryManager.GetGenre(name);
if (!existingGenreIds.Contains(item.Id))
{
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
refreshed++;
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -102,8 +78,6 @@ public class GenresValidator
progress.Report(percent); progress.Report(percent);
} }
_logger.LogInformation("Refreshed metadata for {RefreshedCount} new genres out of {TotalCount} total", refreshed, count);
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre], IncludeItemTypes = [BaseItemKind.Genre, BaseItemKind.MusicGenre],
@@ -114,9 +88,15 @@ public class GenresValidator
foreach (var item in deadEntities) foreach (var item in deadEntities)
{ {
_logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
}
_libraryManager.DeleteItemsUnsafeFast(deadEntities, deleteSourceFiles: true); _libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
progress.Report(100); progress.Report(100);
} }

View File

@@ -1,9 +1,6 @@
using System; using System;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -48,25 +45,17 @@ public class MusicGenresValidator
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var names = _itemRepo.GetMusicGenreNames(); var names = _itemRepo.GetMusicGenreNames();
var existingMusicGenreIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.MusicGenre]
}).ToHashSet();
var numComplete = 0; var numComplete = 0;
var count = names.Count; var count = names.Count;
var refreshed = 0;
foreach (var name in names) foreach (var name in names)
{ {
try try
{ {
var item = _libraryManager.GetMusicGenre(name); var item = _libraryManager.GetMusicGenre(name);
if (!existingMusicGenreIds.Contains(item.Id))
{ await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
refreshed++;
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -86,8 +75,6 @@ public class MusicGenresValidator
progress.Report(percent); progress.Report(percent);
} }
_logger.LogInformation("Refreshed metadata for {RefreshedCount} new music genres out of {TotalCount} total", refreshed, count);
progress.Report(100); progress.Report(100);
} }
} }

View File

@@ -109,7 +109,7 @@ public class PeopleValidator
var i = 0; var i = 0;
foreach (var item in deadEntities.Chunk(500)) foreach (var item in deadEntities.Chunk(500))
{ {
_libraryManager.DeleteItemsUnsafeFast(item, true); _libraryManager.DeleteItemsUnsafeFast(item);
subProgress.Report(100f / deadEntities.Count * (i++ * 100)); subProgress.Report(100f / deadEntities.Count * (i++ * 100));
} }

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@@ -50,40 +49,17 @@ public class StudiosValidator
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{ {
var names = _itemRepo.GetStudioNames(); var names = _itemRepo.GetStudioNames();
var existingStudioIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.Studio]
}).ToHashSet();
var existingStudios = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = [BaseItemKind.Studio]
}).Cast<Studio>()
.GroupBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
var numComplete = 0; var numComplete = 0;
var count = names.Count; var count = names.Count;
var refreshed = 0;
foreach (var name in names) foreach (var name in names)
{ {
try try
{ {
Studio? item = null; var item = _libraryManager.GetStudio(name);
if (existingStudios.TryGetValue(name, out var existingStudio))
{
item = existingStudio;
}
// Fall back to GetStudio if not found (creates new item if needed) await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
item ??= _libraryManager.GetStudio(name);
if (!existingStudioIds.Contains(item.Id))
{
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
refreshed++;
}
} }
catch (OperationCanceledException) catch (OperationCanceledException)
{ {
@@ -103,8 +79,6 @@ public class StudiosValidator
progress.Report(percent); progress.Report(percent);
} }
_logger.LogInformation("Refreshed metadata for {RefreshedCount} new studios out of {TotalCount} total", refreshed, count);
var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery var deadEntities = _libraryManager.GetItemList(new InternalItemsQuery
{ {
IncludeItemTypes = [BaseItemKind.Studio], IncludeItemTypes = [BaseItemKind.Studio],
@@ -115,9 +89,15 @@ public class StudiosValidator
foreach (var item in deadEntities) foreach (var item in deadEntities)
{ {
_logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name); _logger.LogInformation("Deleting dead {ItemType} {ItemId} {ItemName}", item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), item.Name);
}
_libraryManager.DeleteItemsUnsafeFast(deadEntities, deleteSourceFiles: true); _libraryManager.DeleteItem(
item,
new DeleteOptions
{
DeleteFileLocation = false
},
false);
}
progress.Report(100); progress.Report(100);
} }

View File

@@ -1,3 +1,3 @@
{ {
"AppDeviceValues": "Апп: {0}, Априбор: {1}" "Albums": "аальбомқәа"
} }

View File

@@ -1,8 +1,11 @@
{ {
"Artists": "Kunstenare", "Artists": "Kunstenare",
"Channels": "Kanale",
"Folders": "Lêergidse", "Folders": "Lêergidse",
"Favorites": "Gunstelinge", "Favorites": "Gunstelinge",
"HeaderFavoriteShows": "Gunsteling Vertonings", "HeaderFavoriteShows": "Gunsteling Vertonings",
"ValueSpecialEpisodeName": "Spesiale - {0}",
"HeaderAlbumArtists": "Album kunstenaars",
"Books": "Boeke", "Books": "Boeke",
"HeaderNextUp": "Volgende", "HeaderNextUp": "Volgende",
"Movies": "Flieks", "Movies": "Flieks",
@@ -10,13 +13,24 @@
"HeaderContinueWatching": "Hou aan kyk", "HeaderContinueWatching": "Hou aan kyk",
"HeaderFavoriteEpisodes": "Gunsteling Episodes", "HeaderFavoriteEpisodes": "Gunsteling Episodes",
"Photos": "Foto's", "Photos": "Foto's",
"Playlists": "Snitlyste",
"HeaderFavoriteArtists": "Gunsteling Kunstenaars",
"HeaderFavoriteAlbums": "Gunsteling Albums",
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
"DeviceOnlineWithName": "{0} is aanlyn",
"DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings", "Collections": "Versamelings",
"Inherit": "Ontvang", "Inherit": "Ontvang",
"HeaderLiveTV": "Lewendige TV", "HeaderLiveTV": "Lewendige TV",
"Application": "Program",
"AppDeviceValues": "App: {0}, Toestel: {1}", "AppDeviceValues": "App: {0}, Toestel: {1}",
"VersionNumber": "Weergawe {0}", "VersionNumber": "Weergawe {0}",
"ValueHasBeenAddedToLibrary": "{0} is by jou media biblioteek bygevoeg",
"UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel", "UserStoppedPlayingItemWithValues": "{0} het klaar {1} op {2} gespeel",
"UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel", "UserStartedPlayingItemWithValues": "{0} is besig om {1} op {2} te speel",
"UserPolicyUpdatedWithName": "Gebruiker beleid is verander vir {0}",
"UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander", "UserPasswordChangedWithName": "Gebruiker {0} se wagwoord is verander",
"UserOnlineFromDevice": "{0} is aanlyn van {1}", "UserOnlineFromDevice": "{0} is aanlyn van {1}",
"UserOfflineFromDevice": "{0} is ontkoppel van {1}", "UserOfflineFromDevice": "{0} is ontkoppel van {1}",
@@ -24,13 +38,19 @@
"UserDownloadingItemWithValues": "{0} is besig om {1} af te laai", "UserDownloadingItemWithValues": "{0} is besig om {1} af te laai",
"UserDeletedWithName": "Gebruiker {0} is verwyder", "UserDeletedWithName": "Gebruiker {0} is verwyder",
"UserCreatedWithName": "Gebruiker {0} is geskep", "UserCreatedWithName": "Gebruiker {0} is geskep",
"User": "Gebruiker",
"TvShows": "TV Programme", "TvShows": "TV Programme",
"System": "Stelsel",
"SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}", "SubtitleDownloadFailureFromForItem": "Ondertitels het misluk om af te laai van {0} vir {1}",
"StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.", "StartupEmbyServerIsLoading": "Jellyfin Bediener is besig om te laai. Probeer weer in 'n kort tyd.",
"ServerNameNeedsToBeRestarted": "{0} moet herbegin word",
"ScheduledTaskStartedWithName": "{0} het begin",
"ScheduledTaskFailedWithName": "{0} het misluk", "ScheduledTaskFailedWithName": "{0} het misluk",
"ProviderValue": "Voorsiener: {0}",
"PluginUpdatedWithName": "{0} was opgedateer", "PluginUpdatedWithName": "{0} was opgedateer",
"PluginUninstalledWithName": "{0} was verwyder", "PluginUninstalledWithName": "{0} was verwyder",
"PluginInstalledWithName": "{0} is geïnstalleer", "PluginInstalledWithName": "{0} is geïnstalleer",
"Plugin": "Inprop module",
"NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop", "NotificationOptionVideoPlaybackStopped": "Video terugspeel het gestop",
"NotificationOptionVideoPlayback": "Video terugspeel het begin", "NotificationOptionVideoPlayback": "Video terugspeel het begin",
"NotificationOptionUserLockedOut": "Gebruiker uitgeslyt", "NotificationOptionUserLockedOut": "Gebruiker uitgeslyt",
@@ -54,14 +74,23 @@
"MusicVideos": "Musiek Videos", "MusicVideos": "Musiek Videos",
"Music": "Musiek", "Music": "Musiek",
"MixedContent": "Gemengde inhoud", "MixedContent": "Gemengde inhoud",
"MessageServerConfigurationUpdated": "Bediener konfigurasie is opgedateer",
"MessageNamedServerConfigurationUpdatedWithValue": "Bediener konfigurasie seksie {0} is opgedateer",
"MessageApplicationUpdatedTo": "Jellyfin Bediener is opgedateer na {0}",
"MessageApplicationUpdated": "Jellyfin Bediener is opgedateer",
"Latest": "Nuutste", "Latest": "Nuutste",
"LabelRunningTimeValue": "Werktyd: {0}", "LabelRunningTimeValue": "Werktyd: {0}",
"LabelIpAddressValue": "IP adres: {0}", "LabelIpAddressValue": "IP adres: {0}",
"ItemRemovedWithName": "{0} is uit versameling verwyder",
"ItemAddedWithName": "{0} is by die versameling gevoeg",
"HomeVideos": "Tuis Videos", "HomeVideos": "Tuis Videos",
"HeaderRecordingGroups": "Groep Opnames",
"Genres": "Genres", "Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}", "FailedLoginAttemptWithUserName": "Mislukte aanmeldpoging van {0}",
"ChapterNameValue": "Hoofstuk {0}", "ChapterNameValue": "Hoofstuk {0}",
"CameraImageUploadedFrom": "'n Nuwe kamera foto is opgelaai vanaf {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer", "AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
"Albums": "Albums",
"TasksChannelsCategory": "Internet kanale", "TasksChannelsCategory": "Internet kanale",
"TasksApplicationCategory": "aansoek", "TasksApplicationCategory": "aansoek",
"TasksLibraryCategory": "biblioteek", "TasksLibraryCategory": "biblioteek",
@@ -99,12 +128,12 @@
"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.", "TaskAudioNormalizationDescription": "Skandeer lêers vir oudio-normaliseringsdata.",
"TaskAudioNormalization": "Odio Normalisering", "TaskAudioNormalization": "Odio Normalisering",
"TaskCleanCollectionsAndPlaylists": "Maak versamelings en snitlyste skoon",
"TaskCleanCollectionsAndPlaylistsDescription": "Verwyder items uit versamelings en snitlyste wat nie meer bestaan nie.",
"TaskDownloadMissingLyrics": "Laai tekorte lirieke af", "TaskDownloadMissingLyrics": "Laai tekorte lirieke af",
"TaskDownloadMissingLyricsDescription": "Laai lirieke af vir liedjies", "TaskDownloadMissingLyricsDescription": "Laai lirieke af vir liedjies",
"TaskExtractMediaSegments": "Media Segment Skandeer", "TaskExtractMediaSegments": "Media Segment Skandeer",
"TaskExtractMediaSegmentsDescription": "Onttrek of verkry mediasegmente van MediaSegment-geaktiveerde inproppe.", "TaskExtractMediaSegmentsDescription": "Onttrek of verkry mediasegmente van MediaSegment-geaktiveerde inproppe.",
"TaskMoveTrickplayImages": "Migreer Trickplay Beeldligging", "TaskMoveTrickplayImages": "Migreer Trickplay Beeldligging",
"TaskMoveTrickplayImagesDescription": "Skuif ontstaande trickplay lêers volgens die biblioteekinstellings.", "TaskMoveTrickplayImagesDescription": "Skuif ontstaande trickplay lêers volgens die biblioteekinstellings."
"CleanupUserDataTask": "Gebruikers data skoon maak taak",
"CleanupUserDataTaskDescription": "Maak alle gebruikers data (kykstatus, gunstelingstatus, ens.) skoon van media wat nie meer vir ten minste 90 dae teenwoordig is nie."
} }

View File

@@ -1,112 +1,141 @@
{ {
"AppDeviceValues": "التطبيق: {0}، الجهاز: {1}", "Albums": "ألبومات",
"Artists": "الفنانون", "AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
"AuthenticationSucceededWithUserName": "تمت مصادقة {0} بنجاح", "Application": "تطبيق",
"Artists": "فنانون",
"AuthenticationSucceededWithUserName": "نجحت عملية التوثيق بـ {0}",
"Books": "الكتب", "Books": "الكتب",
"CameraImageUploadedFrom": "رُفعت صورة الكاميرا الجديدة من {0}",
"Channels": "القنوات",
"ChapterNameValue": "الفصل {0}", "ChapterNameValue": "الفصل {0}",
"Collections": "المجموعات", "Collections": "مجموعات",
"FailedLoginAttemptWithUserName": "محاولة تسجيل دخول فاشلة من {0}", "DeviceOfflineWithName": "قُطِع الاتصال ب{0}",
"DeviceOnlineWithName": "{0} متصل",
"FailedLoginAttemptWithUserName": "محاولة تسجيل الدخول فاشلة من {0}",
"Favorites": "المفضلة", "Favorites": "المفضلة",
"Folders": "المجلدات", "Folders": "المجلدات",
"Genres": "الأنواع", "Genres": "التصنيفات",
"HeaderAlbumArtists": "فناني الألبوم",
"HeaderContinueWatching": "متابعة المشاهدة", "HeaderContinueWatching": "متابعة المشاهدة",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
"HeaderFavoriteEpisodes": "الحلقات المفضلة", "HeaderFavoriteEpisodes": "الحلقات المفضلة",
"HeaderFavoriteShows": "المسلسلات المفضلة", "HeaderFavoriteShows": "المسلسلات المفضلة",
"HeaderLiveTV": "البث التلفزيوني المباشر", "HeaderFavoriteSongs": "الأغاني المفضلة",
"HeaderLiveTV": "التلفاز المباشر",
"HeaderNextUp": "التالي", "HeaderNextUp": "التالي",
"HomeVideos": "فيديوهات منزلية", "HeaderRecordingGroups": "مجموعات التسجيل",
"Inherit": "وراثة", "HomeVideos": "الفيديوهات الشخصية",
"LabelIpAddressValue": "عنوان IP: {0}", "Inherit": "توريث",
"ItemAddedWithName": "أُضيف {0} للمكتبة",
"ItemRemovedWithName": "أُزيل {0} من المكتبة",
"LabelIpAddressValue": "عنوان الآي بي: {0}",
"LabelRunningTimeValue": "مدة التشغيل: {0}", "LabelRunningTimeValue": "مدة التشغيل: {0}",
"Latest": "الأحدث", "Latest": "الأحدث",
"MessageApplicationUpdated": "حُدث خادم Jellyfin",
"MessageApplicationUpdatedTo": "حُدث خادم Jellyfin إلى {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "حُدثت إعدادات الخادم في قسم {0}",
"MessageServerConfigurationUpdated": "حُدثت إعدادات الخادم",
"MixedContent": "محتوى مختلط", "MixedContent": "محتوى مختلط",
"Movies": "الأفلام", "Movies": "الأفلام",
"Music": "الموسيقى", "Music": "الموسيقى",
"MusicVideos": "الفيديوهات الموسيقية", "MusicVideos": "الفيديوهات الموسيقية",
"NameInstallFailed": "فشل تثبيت {0}", "NameInstallFailed": "فشل تثبيت {0}",
"NameSeasonNumber": "الموسم {0}", "NameSeasonNumber": "الموسم {0}",
"NameSeasonUnknown": "موسم غير معروف", "NameSeasonUnknown": "الموسم غير معروف",
"NewVersionIsAvailable": "يتوفر إصدار جديد من خادم Jellyfin للتنزيل.", "NewVersionIsAvailable": "نسخة جديدة من خادم Jellyfin متوفرة للتحميل.",
"NotificationOptionApplicationUpdateAvailable": "تحديث التطبيق متاح", "NotificationOptionApplicationUpdateAvailable": "يوجد تحديث للتطبيق",
"NotificationOptionApplicationUpdateInstalled": "تم تثبيت تحديث التطبيق", "NotificationOptionApplicationUpdateInstalled": "نُصب تحديث التطبيق",
"NotificationOptionAudioPlayback": "بدأ تشغيل الصوت", "NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
"NotificationOptionAudioPlaybackStopped": "توقف تشغيل الصوت", "NotificationOptionAudioPlaybackStopped": "أُوقف تشغيل المقطع الصوتي",
"NotificationOptionCameraImageUploaded": "تم رفع صورة كاميرا", "NotificationOptionCameraImageUploaded": ُفعت صورة الكاميرا",
"NotificationOptionInstallationFailed": "فشل التثبيت", "NotificationOptionInstallationFailed": "فشل في التثبيت",
"NotificationOptionNewLibraryContent": "تمت إضافة محتوى جديد", "NotificationOptionNewLibraryContent": "أُضيف محتوى جديدا",
"NotificationOptionPluginError": "خطأ في الملحق", "NotificationOptionPluginError": "فشل في الملحق",
"NotificationOptionPluginInstalled": م تثبيت الملحق", "NotificationOptionPluginInstalled": "ثُبتت الملحق",
"NotificationOptionPluginUninstalled": "تمت إزالة الملحق", "NotificationOptionPluginUninstalled": "تمت إزالة الملحق",
"NotificationOptionPluginUpdateInstalled": "تم تحديث الملحق", "NotificationOptionPluginUpdateInstalled": "تم تثبيت تحديثات الملحق",
"NotificationOptionServerRestartRequired": "مطلوب إعادة تشغيل الخادم", "NotificationOptionServerRestartRequired": "يجب إعادة تشغيل الخادم",
"NotificationOptionTaskFailed": "فشل المهمة المجدولة", "NotificationOptionTaskFailed": "فشل في المهمة المجدولة",
"NotificationOptionUserLockedOut": "تم قفل حساب المستخدم", "NotificationOptionUserLockedOut": "تم إقفال حساب المستخدم",
"NotificationOptionVideoPlayback": "بدأ تشغيل الفيديو", "NotificationOptionVideoPlayback": "بدأ تشغيل الفيديو",
"NotificationOptionVideoPlaybackStopped": وقف تشغيل الفيديو", "NotificationOptionVideoPlaybackStopped": م إيقاف تشغيل الفيديو",
"Photos": "الصور", "Photos": "الصور",
"Playlists": "قوائم التشغيل",
"Plugin": "الملحق",
"PluginInstalledWithName": "تم تثبيت {0}", "PluginInstalledWithName": "تم تثبيت {0}",
"PluginUninstalledWithName": "تمت إزالة {0}", "PluginUninstalledWithName": "تمت إزالة {0}",
"PluginUpdatedWithName": "تم تحديث {0}", "PluginUpdatedWithName": "تم تحديث {0}",
"ScheduledTaskFailedWithName": "فشلت {0}", "ProviderValue": "المزود: {0}",
"Shows": "المسلسلات", "ScheduledTaskFailedWithName": "فشلت العملية {0}",
"StartupEmbyServerIsLoading": "يتم الآن تحميل خادم Jellyfin. يرجى المحاولة مرة أخرى بعد قليل.", "ScheduledTaskStartedWithName": "تم بدء العملية {0}",
"SubtitleDownloadFailureFromForItem": "فشل تنزيل الترجمات من {0} لـ {1}", "ServerNameNeedsToBeRestarted": "يحتاج {0} لإعادة التشغيل",
"Shows": "العروض",
"Songs": "الأغاني",
"StartupEmbyServerIsLoading": "يتم تحميل خادم Jellyfin . الرجاء المحاولة بعد قليل.",
"SubtitleDownloadFailureFromForItem": "فشل تحميل الترجمات من {0} ل {1}",
"Sync": "مزامنة",
"System": "النظام",
"TvShows": "البرامج التلفزيونية", "TvShows": "البرامج التلفزيونية",
"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}",
"UserStartedPlayingItemWithValues": "{0} يقوم بتشغيل {1} على {2}", "UserPolicyUpdatedWithName": "تم تحديث سياسة المستخدم {0}",
"UserStoppedPlayingItemWithValues": "أنهى {0} تشغيل {1} على {2}", "UserStartedPlayingItemWithValues": "قام {0} ببدء تشغيل {1} على {2}",
"UserStoppedPlayingItemWithValues": "قام {0} بإيقاف تشغيل {1} على {2}",
"ValueHasBeenAddedToLibrary": "تمت اضافت {0} إلى مكتبة الوسائط",
"ValueSpecialEpisodeName": "حلقة خاصه - {0}",
"VersionNumber": "الإصدار {0}", "VersionNumber": "الإصدار {0}",
"TaskCleanCacheDescription": "يحذف ملفات ذاكرة التخزين المؤقت التي لم يعد النظام بحاجة إليها.", "TaskCleanCacheDescription": "يحذف الملفات المؤقتة التي لم يعد النظام بحاجة إليها.",
"TaskCleanCache": "تنظيف مجلد ذاكرة التخزين المؤقت", "TaskCleanCache": "حذف الملفات المؤقتة",
"TasksChannelsCategory": "قنوات الإنترنت", "TasksChannelsCategory": "قنوات الإنترنت",
"TasksLibraryCategory": "المكتبة", "TasksLibraryCategory": "مكتبة",
"TasksMaintenanceCategory": "الصيانة", "TasksMaintenanceCategory": "صيانة",
"TaskRefreshLibraryDescription": "يفحص مكتبة المحتوى الخاصة بك بحثاً عن ملفات جديدة ويحدّث البيانات الوصفية.", "TaskRefreshLibraryDescription": "يفحص مكتبة الوسائط الخاصة بك باحثا عن ملفات جديدة، ومن ثم يُحدث البيانات الوصفية.",
"TaskRefreshLibrary": "فحص مكتبة المحتوى", "TaskRefreshLibrary": "افحص مكتبة الوسائط",
"TaskRefreshChapterImagesDescription": "ينشئ صوراً مصغرة للفيديوهات التي تحتوي على فصول.", "TaskRefreshChapterImagesDescription": ُنشئ صور مصغرة لمقاطع الفيديو التي تحتوي على فصول.",
"TaskRefreshChapterImages": "استخراج صور الفصول", "TaskRefreshChapterImages": "استخراج صور الفصل",
"TasksApplicationCategory": "التطبيق", "TasksApplicationCategory": "تطبيق",
"TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت عن الترجمات المفقودة بناءً على إعدادات البيانات الوصفية.", "TaskDownloadMissingSubtitlesDescription": "يبحث في الإنترنت على الترجمات الناقصة استنادا على البيانات الوصفية.",
"TaskDownloadMissingSubtitles": نزيل الترجمات المفقودة", "TaskDownloadMissingSubtitles": حميل الترجمات الناقصة",
"TaskRefreshChannelsDescription": "يحدّث معلومات قنوات الإنترنت.", "TaskRefreshChannelsDescription": "يحدث معلومات قنوات الإنترنت.",
"TaskRefreshChannels": "تحديث القنوات", "TaskRefreshChannels": "إعادة تحديث القنوات",
"TaskCleanTranscodeDescription": "يحذف ملفات تحويل الترميز التي مر عليها أكثر من يوم واحد.", "TaskCleanTranscodeDescription": "يحذف ملفات الترميز الأقدم من يوم واحد.",
"TaskCleanTranscode": "تنظيف مجلد تحويل الترميز", "TaskCleanTranscode": "حذف ما بمجلد الترميز",
"TaskUpdatePluginsDescription": نزّل ويثبّت التحديثات للملحقات المهيأة للتحديث التلقائي.", "TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
"TaskUpdatePlugins": "تحديث الملحقات", "TaskUpdatePlugins": "تحديث الإضافات",
"TaskRefreshPeopleDescription": "يحدّث البيانات الوصفية للممثلين والمخرجين في مكتبة المحتوى الخاصة بك.", "TaskRefreshPeopleDescription": قوم بتحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
"TaskRefreshPeople": "تحديث الأشخاص", "TaskRefreshPeople": "إعادة تحميل الأشخاص",
"TaskCleanLogsDescription": "يحذف ملفات السجل التي يزيد عمرها عن {0} أيام.", "TaskCleanLogsDescription": "يحذف السجلات الأقدم من {0} يوم.",
"TaskCleanLogs": "تنظيف مجلد السجلات", "TaskCleanLogs": "حذف مسار السجل",
"TaskCleanActivityLogDescription": "يحذف إدخالات سجل النشاط الأقدم من العمر المحدد.", "TaskCleanActivityLogDescription": "يحذف سجل الأنشطة الأقدم من الوقت الذي تم تحديده.",
"TaskCleanActivityLog": "تنظيف سجل النشاط", "TaskCleanActivityLog": "حذف سجل الأنشطة",
"Default": "الافتراضي", "Default": "افتراضي",
"Undefined": "غير محدد", "Undefined": "غير معرف",
"Forced": "إجباري", "Forced": "ملحقة",
"TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقلل المساحة الحرة. قد يؤدي تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات على قاعدة البيانات إلى تحسين الأداء.", "TaskOptimizeDatabaseDescription": "يضغط قاعدة البيانات ويقتطع المساحة الحرة. تشغيل هذه المهمة بعد فحص المكتبة أو إجراء تغييرات أخرى تتضمن تعديلات في قاعدة البيانات قد تؤدي إلى تحسين الأداء.",
"TaskOptimizeDatabase": "تحسين قاعدة البيانات", "TaskOptimizeDatabase": "تحسين قاعدة البيانات",
"TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لإنشاء قوائم تشغيل HLS أكثر دقة. قد يستغرق تشغيل هذه المهمة وقتاً طويلاً.", "TaskKeyframeExtractorDescription": "يستخرج الإطارات الرئيسية من ملفات الفيديو لكي ينشئ قوائم تشغيل بث HTTP المباشر. قد تستمر هذه العملية لوقت طويل.",
"TaskKeyframeExtractor": "مستخرج الإطارات الرئيسية", "TaskKeyframeExtractor": "مستخرج الإطار الرئيسي",
"External": "خارجي", "External": "خارجي",
"HearingImpaired": "لضعاف السمع", "HearingImpaired": "ضعاف السمع",
"TaskRefreshTrickplayImages": "إنشاء صور معاينات التنقل (Trickplay)", "TaskRefreshTrickplayImages": "توليد صور المعاينة السريعة",
"TaskRefreshTrickplayImagesDescription": نشئ صور معاينات التنقل السريع للفيديوهات في المكتبات المفعّلة.", "TaskRefreshTrickplayImagesDescription": ُولّد معاينات تنقل سريع لمقاطع الفيديو ضمن المكتبات المفعّلة.",
"TaskAudioNormalization": "تطبيع الصوت", "TaskCleanCollectionsAndPlaylists": "حذف المجموعات وقوائم التشغيل",
"TaskAudioNormalizationDescription": "يفحص الملفات لجمع بيانات تطبيع الصوت.", "TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.",
"TaskDownloadMissingLyrics": "تنزيل الكلمات المفقودة", "TaskAudioNormalization": "تسوية الصوت",
"TaskDownloadMissingLyricsDescription": "ينزّل الكلمات للأغاني.", "TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت.",
"TaskExtractMediaSegments": "فحص مقاطع المحتوى", "TaskDownloadMissingLyrics": "تنزيل عبارات القصيدة",
"TaskExtractMediaSegmentsDescription": "يستخرج أو يحصل على مقاطع المحتوى من الملحقات المفعّلة لمقاطع المحتوى (MediaSegment).", "TaskDownloadMissingLyricsDescription": "كلمات",
"TaskMoveTrickplayImages": "نقل موقع صور معاينات التنقل", "TaskExtractMediaSegments": "فحص مقاطع الوسائط",
"TaskMoveTrickplayImagesDescription": نقل ملفات معاينات التنقل الحالية وفقاً لإعدادات المكتبة.", "TaskExtractMediaSegmentsDescription": ستخرج مقاطع وسائط من إضافات MediaSegment المُفعّلة.",
"TaskMoveTrickplayImages": "تغيير مكان صور المعاينة السريعة",
"TaskMoveTrickplayImagesDescription": "تُنقل ملفات التشغيل السريع الحالية بناءً على إعدادات المكتبة.",
"CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم", "CleanupUserDataTask": "مهمة تنظيف بيانات المستخدم",
"CleanupUserDataTaskDescription": "ينظف جميع بيانات المستخدم (مثل حالة المشاهدة وحالة المفضلة وغيرها) للمحتوى الذي لم يعد موجوداً لمدة 90 يوماً على الأقل.", "CleanupUserDataTaskDescription": "مسح جميع بيانات المستخدم (حالة المشاهدة، والحالة المفضلة وما إلى ذلك) من الوسائط التي لم تعد موجودة لمدة 90 يومًا على الأقل."
"Original": "فريد",
"LyricDownloadFailureFromForItem": "فشل تحميل الكلمات من {0} إلى {1}"
} }

View File

@@ -1,13 +1,18 @@
{ {
"Albums": "এলবাম",
"Application": "আবেদন",
"AppDeviceValues": "এপ্‌: {0}, ডিভাইচ: {1}", "AppDeviceValues": "এপ্‌: {0}, ডিভাইচ: {1}",
"Artists": "শিল্পী", "Artists": "শিল্পী",
"Channels": "চেনেলস",
"Default": "ডিফল্ট", "Default": "ডিফল্ট",
"AuthenticationSucceededWithUserName": "{0} সফলভাবে প্রমাণিত", "AuthenticationSucceededWithUserName": "{0} সফলভাবে প্রমাণিত",
"Books": "পুস্তক", "Books": "পুস্তক",
"Movies": "চলচ্চিত্ৰ", "Movies": "চলচ্চিত্ৰ",
"CameraImageUploadedFrom": "একটি নতুন ক্যামেরা চিত্র আপলোড করা হয়েছে {0}",
"Collections": "সংগ্রহ", "Collections": "সংগ্রহ",
"HeaderFavoriteShows": "প্রিয় শোসমূহ", "HeaderFavoriteShows": "প্রিয় শোসমূহ",
"Latest": "শেহতীয়া", "Latest": "শেহতীয়া",
"MessageApplicationUpdated": "জেলিফিন চাইভাৰ আপডেট কৰা হৈছে",
"MixedContent": "মিশ্ৰিত সমগ্ৰতা", "MixedContent": "মিশ্ৰিত সমগ্ৰতা",
"NewVersionIsAvailable": "ডাউনলোড কৰিবলৈ জেলিফিন চাইভাৰৰ এটা নতুন সংস্কৰণ উপলব্ধ আছে.", "NewVersionIsAvailable": "ডাউনলোড কৰিবলৈ জেলিফিন চাইভাৰৰ এটা নতুন সংস্কৰণ উপলব্ধ আছে.",
"NotificationOptionCameraImageUploaded": "কেমেৰাৰ চিত্ৰ আপল'ড কৰা হ'ল", "NotificationOptionCameraImageUploaded": "কেমেৰাৰ চিত্ৰ আপল'ড কৰা হ'ল",
@@ -16,14 +21,20 @@
"Folders": "ফোল্ডাৰ", "Folders": "ফোল্ডাৰ",
"Forced": "বলপূর্বক", "Forced": "বলপূর্বক",
"Genres": "শ্রেণী", "Genres": "শ্রেণী",
"HeaderAlbumArtists": "অ্যালবাম শিল্পী",
"HeaderContinueWatching": "দেখা চালিয়ে যান", "HeaderContinueWatching": "দেখা চালিয়ে যান",
"FailedLoginAttemptWithUserName": "লগইন ব্যর্থ চেষ্টা কৰা হৈছে থেকে {0}", "FailedLoginAttemptWithUserName": "লগইন ব্যর্থ চেষ্টা কৰা হৈছে থেকে {0}",
"HeaderFavoriteAlbums": "প্রিয় অ্যালবামসমূহ",
"HeaderFavoriteArtists": "প্রিয় শিল্পীসমূহ",
"HeaderFavoriteEpisodes": "প্রিয় পর্বসমূহ", "HeaderFavoriteEpisodes": "প্রিয় পর্বসমূহ",
"HeaderFavoriteSongs": "প্ৰিয় গীত",
"HeaderLiveTV": "প্ৰতিবেদন টিভি", "HeaderLiveTV": "প্ৰতিবেদন টিভি",
"HeaderNextUp": "পৰৱৰ্তী অংশ", "HeaderNextUp": "পৰৱৰ্তী অংশ",
"HeaderRecordingGroups": "অলংকৰণ গোষ্ঠীসমূহ",
"HearingImpaired": "শ্ৰবণ অক্ষম", "HearingImpaired": "শ্ৰবণ অক্ষম",
"HomeVideos": "ঘৰৰ ভিডিঅ'সমূহ", "HomeVideos": "ঘৰৰ ভিডিঅ'সমূহ",
"Inherit": "উত্তপ্ত কৰা", "Inherit": "উত্তপ্ত কৰা",
"MessageServerConfigurationUpdated": "চাইভাৰ কনফিগাৰেশ্যন আপডেট কৰা হৈছে",
"NotificationOptionApplicationUpdateAvailable": "অ্যাপ্লিকেশ্যন আপডেট উপলব্ধ", "NotificationOptionApplicationUpdateAvailable": "অ্যাপ্লিকেশ্যন আপডেট উপলব্ধ",
"NotificationOptionApplicationUpdateInstalled": "অ্যাপ্লিকেশ্যন আপডেট ইনষ্টল কৰা হ'ল", "NotificationOptionApplicationUpdateInstalled": "অ্যাপ্লিকেশ্যন আপডেট ইনষ্টল কৰা হ'ল",
"NotificationOptionAudioPlayback": "অডিঅ' প্লেবেক আৰম্ভ হ'ল", "NotificationOptionAudioPlayback": "অডিঅ' প্লেবেক আৰম্ভ হ'ল",

View File

@@ -1,24 +1,36 @@
{ {
"Sync": "Сінхранізаваць",
"Playlists": "Плэй-лісты",
"Latest": "Апошняе", "Latest": "Апошняе",
"LabelIpAddressValue": "IP-адрас: {0}", "LabelIpAddressValue": "IP-адрас: {0}",
"ItemAddedWithName": "{0} даданы ў бібліятэку",
"MessageApplicationUpdated": "Сервер Jellyfin абноўлены",
"NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана", "NotificationOptionApplicationUpdateInstalled": "Абнаўленне праграмы ўсталявана",
"PluginInstalledWithName": "{0} быў усталяваны", "PluginInstalledWithName": "{0} быў усталяваны",
"UserCreatedWithName": "Карыстальнік {0} быў створаны", "UserCreatedWithName": "Карыстальнік {0} быў створаны",
"Albums": "Альбомы",
"Application": "Праграма",
"AuthenticationSucceededWithUserName": "{0} паспяхова аўтарызаваны", "AuthenticationSucceededWithUserName": "{0} паспяхова аўтарызаваны",
"Channels": "Каналы",
"ChapterNameValue": "Раздзел {0}", "ChapterNameValue": "Раздзел {0}",
"Collections": "Калекцыі", "Collections": "Калекцыі",
"Default": радвызначана", "Default": а змаўчанні",
"FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}", "FailedLoginAttemptWithUserName": "Няўдалая спроба ўваходу з {0}",
"Folders": "Папкі", "Folders": "Папкі",
"Favorites": "Абранае", "Favorites": "Абранае",
"External": "Знешні", "External": "Знешні",
"Genres": "Жанры", "Genres": "Жанры",
"HeaderContinueWatching": "Працягнуць прагляд", "HeaderContinueWatching": "Працягнуць прагляд",
"HeaderFavoriteAlbums": "Абраныя альбомы",
"HeaderFavoriteEpisodes": "Абраныя серыі", "HeaderFavoriteEpisodes": "Абраныя серыі",
"HeaderFavoriteShows": "Абраныя шоу", "HeaderFavoriteShows": "Абраныя шоу",
"HeaderFavoriteSongs": "Абраныя песні",
"HeaderLiveTV": "Прамы эфір", "HeaderLiveTV": "Прамы эфір",
"HeaderAlbumArtists": "Выканаўцы альбома",
"LabelRunningTimeValue": "Працягласць: {0}", "LabelRunningTimeValue": "Працягласць: {0}",
"HomeVideos": "Хатнія відэа", "HomeVideos": "Хатнія відэа",
"ItemRemovedWithName": "{0} выдалены з бібліятэкі",
"MessageApplicationUpdatedTo": "Сервер Jellyfin абноўлены да версіі {0}",
"Movies": "Фільмы", "Movies": "Фільмы",
"Music": "Музыка", "Music": "Музыка",
"MusicVideos": "Музычныя кліпы", "MusicVideos": "Музычныя кліпы",
@@ -29,13 +41,19 @@
"NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна ўсталявана", "NotificationOptionPluginUpdateInstalled": "Абнаўленне плагіна ўсталявана",
"NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера", "NotificationOptionServerRestartRequired": "Патрабуецца перазапуск сервера",
"Photos": "Фотаздымкі", "Photos": "Фотаздымкі",
"Plugin": "Плагін",
"PluginUninstalledWithName": "{0} быў выдалены", "PluginUninstalledWithName": "{0} быў выдалены",
"PluginUpdatedWithName": "{0} быў абноўлены", "PluginUpdatedWithName": "{0} быў абноўлены",
"ProviderValue": "Пастаўшчык: {0}",
"Songs": "Песні",
"System": "Сістэма",
"User": "Карыстальнік",
"UserDeletedWithName": "Карыстальнік {0} быў выдалены", "UserDeletedWithName": "Карыстальнік {0} быў выдалены",
"UserDownloadingItemWithValues": "{0} спампоўваецца {1}", "UserDownloadingItemWithValues": "{0} спампоўваецца {1}",
"TaskOptimizeDatabase": "Аптымізацыя базы даных", "TaskOptimizeDatabase": "Аптымізацыя базы даных",
"Artists": "Выканаўцы", "Artists": "Выканаўцы",
"UserOfflineFromDevice": "{0} адлучыўся ад {1}", "UserOfflineFromDevice": "{0} адлучыўся ад {1}",
"UserPolicyUpdatedWithName": "Палітыка карыстальніка абноўлена для {0}",
"TaskCleanActivityLogDescription": "Выдаляе запісы старэйшыя за зададзены ўзрост ў журнале актыўнасці.", "TaskCleanActivityLogDescription": "Выдаляе запісы старэйшыя за зададзены ўзрост ў журнале актыўнасці.",
"TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.", "TaskRefreshChapterImagesDescription": "Стварае мініяцюры для відэа, якія маюць раздзелы.",
"TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.", "TaskCleanLogsDescription": "Выдаляе файлы журналу, якім больш за {0} дзён.",
@@ -47,17 +65,24 @@
"TasksApplicationCategory": "Праграма", "TasksApplicationCategory": "Праграма",
"AppDeviceValues": "Праграма: {0}, Прылада: {1}", "AppDeviceValues": "Праграма: {0}, Прылада: {1}",
"Books": "Кнігі", "Books": "Кнігі",
"CameraImageUploadedFrom": "Новая выява камеры была загружана з {0}",
"DeviceOfflineWithName": "{0} адлучыўся",
"DeviceOnlineWithName": "{0} падлучаны",
"Forced": "Прымусова", "Forced": "Прымусова",
"HeaderRecordingGroups": "Групы запісаў",
"HeaderNextUp": "Наступнае", "HeaderNextUp": "Наступнае",
"HeaderFavoriteArtists": "Абраныя выканаўцы",
"HearingImpaired": "Са слабым слыхам", "HearingImpaired": "Са слабым слыхам",
"Inherit": "Атрымаць у спадчыну", "Inherit": "Атрымаць у спадчыну",
"MessageNamedServerConfigurationUpdatedWithValue": "Канфігурацыя сервера (секцыя {0}) абноўлена",
"MessageServerConfigurationUpdated": "Канфігурацыя сервера абноўлена",
"MixedContent": "Змешаны змест", "MixedContent": "Змешаны змест",
"NameSeasonUnknown": "Невядомы сезон", "NameSeasonUnknown": "Невядомы сезон",
"NotificationOptionInstallationFailed": "Збой усталёўкі", "NotificationOptionInstallationFailed": "Збой усталёўкі",
"NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.", "NewVersionIsAvailable": "Новая версія сервера Jellyfin даступная для cпампоўкі.",
"NotificationOptionCameraImageUploaded": "Выява камеры запампавана", "NotificationOptionCameraImageUploaded": "Выява камеры запампавана",
"NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыя спынена", "NotificationOptionAudioPlaybackStopped": "Прайграванне аўдыё спынена",
"NotificationOptionAudioPlayback": "Прайграванне аўдыя пачалося", "NotificationOptionAudioPlayback": "Прайграванне аўдыё пачалося",
"NotificationOptionNewLibraryContent": "Дададзены новы кантэнт", "NotificationOptionNewLibraryContent": "Дададзены новы кантэнт",
"NotificationOptionPluginError": "Збой плагіна", "NotificationOptionPluginError": "Збой плагіна",
"NotificationOptionPluginUninstalled": "Плагін выдалены", "NotificationOptionPluginUninstalled": "Плагін выдалены",
@@ -66,6 +91,8 @@
"NotificationOptionVideoPlayback": "Пачалося прайграванне відэа", "NotificationOptionVideoPlayback": "Пачалося прайграванне відэа",
"NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена", "NotificationOptionVideoPlaybackStopped": "Прайграванне відэа спынена",
"ScheduledTaskFailedWithName": "{0} не атрымалася", "ScheduledTaskFailedWithName": "{0} не атрымалася",
"ScheduledTaskStartedWithName": "{0} пачалося",
"ServerNameNeedsToBeRestarted": "{0} патрабуе перазапуску",
"Shows": "Шоу", "Shows": "Шоу",
"StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.", "StartupEmbyServerIsLoading": "Jellyfin Server загружаецца. Калі ласка, паўтарыце спробу крыху пазней.",
"SubtitleDownloadFailureFromForItem": "Субцітры для {1} не ўдалося спампаваць з {0}", "SubtitleDownloadFailureFromForItem": "Субцітры для {1} не ўдалося спампаваць з {0}",
@@ -76,6 +103,8 @@
"UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}", "UserPasswordChangedWithName": "Пароль быў зменены для карыстальніка {0}",
"UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}", "UserStartedPlayingItemWithValues": "{0} прайграваецца {1} на {2}",
"UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}", "UserStoppedPlayingItemWithValues": "{0} скончыў прайграванне {1} на {2}",
"ValueHasBeenAddedToLibrary": "{0} быў дададзены ў вашу медыятэку",
"ValueSpecialEpisodeName": "Спецвыпуск - {0}",
"VersionNumber": "Версія {0}", "VersionNumber": "Версія {0}",
"TasksMaintenanceCategory": "Абслугоўванне", "TasksMaintenanceCategory": "Абслугоўванне",
"TasksLibraryCategory": "Бібліятэка", "TasksLibraryCategory": "Бібліятэка",
@@ -94,9 +123,11 @@
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.", "TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
"TaskRefreshChannels": "Абнавіць каналы", "TaskRefreshChannels": "Абнавіць каналы",
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры", "TaskDownloadMissingSubtitles": "Спампаваць адсутныя субцітры",
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа выконвацца доўга.", "TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных плэй-лістоў HLS. Гэта задача можа працягнуцца шмат часу.",
"TaskRefreshTrickplayImages": "Стварыць выявы Trickplay", "TaskRefreshTrickplayImages": "Стварыць выявы Trickplay",
"TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.", "TaskRefreshTrickplayImagesDescription": "Стварае перадпрагляды відэаролікаў для Trickplay у падключаных бібліятэках.",
"TaskCleanCollectionsAndPlaylists": "Ачысціце калекцыі і плэй-лісты",
"TaskCleanCollectionsAndPlaylistsDescription": "Выдаляе элементы з калекцый і плэй-лістоў, якія больш не існуюць.",
"TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.", "TaskAudioNormalizationDescription": "Скануе файлы на прадмет нармалізацыі гуку.",
"TaskAudioNormalization": "Нармалізацыя гуку", "TaskAudioNormalization": "Нармалізацыя гуку",
"TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.", "TaskExtractMediaSegmentsDescription": "Выдае або атрымлівае медыясегменты з убудоў з падтрымкай MediaSegment.",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Албуми",
"AppDeviceValues": "Програма: {0}, Устройство: {1}", "AppDeviceValues": "Програма: {0}, Устройство: {1}",
"Application": "Програма",
"Artists": "Артисти", "Artists": "Артисти",
"AuthenticationSucceededWithUserName": "{0} се удостовери успешно", "AuthenticationSucceededWithUserName": "{0} се удостовери успешно",
"Books": "Книги", "Books": "Книги",
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
"Channels": "Канали",
"ChapterNameValue": "Глава {0}", "ChapterNameValue": "Глава {0}",
"Collections": "Колекции", "Collections": "Колекции",
"DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан",
"FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}", "FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}",
"Favorites": "Любими", "Favorites": "Любими",
"Folders": "Папки", "Folders": "Папки",
"Genres": "Жанрове", "Genres": "Жанрове",
"HeaderAlbumArtists": "Изпълнители на албуми",
"HeaderContinueWatching": "Продължаване на гледането", "HeaderContinueWatching": "Продължаване на гледането",
"HeaderFavoriteAlbums": "Любими албуми",
"HeaderFavoriteArtists": "Любими изпълнители",
"HeaderFavoriteEpisodes": "Любими епизоди", "HeaderFavoriteEpisodes": "Любими епизоди",
"HeaderFavoriteShows": "Любими сериали", "HeaderFavoriteShows": "Любими сериали",
"HeaderFavoriteSongs": "Любими песни",
"HeaderLiveTV": "Телевизия на живо", "HeaderLiveTV": "Телевизия на живо",
"HeaderNextUp": "Следва", "HeaderNextUp": "Следва",
"HeaderRecordingGroups": "Запис групи",
"HomeVideos": "Домашни Клипове", "HomeVideos": "Домашни Клипове",
"Inherit": "Наследяване", "Inherit": "Наследяване",
"ItemAddedWithName": "{0} е добавено към библиотеката",
"ItemRemovedWithName": "{0} е премахнато от библиотеката",
"LabelIpAddressValue": "IP адрес: {0}", "LabelIpAddressValue": "IP адрес: {0}",
"LabelRunningTimeValue": "Продължителност: {0}", "LabelRunningTimeValue": "Продължителност: {0}",
"Latest": "Последни", "Latest": "Последни",
"MessageApplicationUpdated": "Сървърът беше обновен",
"MessageApplicationUpdatedTo": "Сървърът беше обновен до {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Секцията {0} от сървърната конфигурация беше актуализирана",
"MessageServerConfigurationUpdated": "Конфигурацията на сървъра беше актуализирана",
"MixedContent": "Смесено съдържание", "MixedContent": "Смесено съдържание",
"Movies": "Филми", "Movies": "Филми",
"Music": "Музика", "Music": "Музика",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна", "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки", "Photos": "Снимки",
"Playlists": "Списъци",
"Plugin": "Добавка",
"PluginInstalledWithName": "{0} е инсталиранa", "PluginInstalledWithName": "{0} е инсталиранa",
"PluginUninstalledWithName": "{0} е деинсталиранa", "PluginUninstalledWithName": "{0} е деинсталиранa",
"PluginUpdatedWithName": "{0} е обновенa", "PluginUpdatedWithName": "{0} е обновенa",
"ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали", "ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна",
"ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира",
"Shows": "Сериали", "Shows": "Сериали",
"Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени", "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени",
"Sync": "Синхронизиране",
"System": "Система",
"TvShows": "Телевизионни сериали", "TvShows": "Телевизионни сериали",
"User": "Потребител",
"UserCreatedWithName": "Потребителят {0} е създаден", "UserCreatedWithName": "Потребителят {0} е създаден",
"UserDeletedWithName": "Потребителят {0} е изтрит", "UserDeletedWithName": "Потребителят {0} е изтрит",
"UserDownloadingItemWithValues": "{0} изтегля {1}", "UserDownloadingItemWithValues": "{0} изтегля {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} се разкачи от {1}", "UserOfflineFromDevice": "{0} се разкачи от {1}",
"UserOnlineFromDevice": "{0} е на линия от {1}", "UserOnlineFromDevice": "{0} е на линия от {1}",
"UserPasswordChangedWithName": "Паролата на потребителя {0} е променена", "UserPasswordChangedWithName": "Паролата на потребителя {0} е променена",
"UserPolicyUpdatedWithName": "Потребителската политика за {0} се актуализира",
"UserStartedPlayingItemWithValues": "{0} пусна {1}", "UserStartedPlayingItemWithValues": "{0} пусна {1}",
"UserStoppedPlayingItemWithValues": "{0} спря {1}", "UserStoppedPlayingItemWithValues": "{0} спря {1}",
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}", "VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.", "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.",
"TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри", "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри",
@@ -99,6 +128,8 @@
"TaskRefreshTrickplayImagesDescription": "Създава прегледи на Trickplay за видеа в активирани библиотеки.", "TaskRefreshTrickplayImagesDescription": "Създава прегледи на Trickplay за видеа в активирани библиотеки.",
"TaskDownloadMissingLyrics": "Свали липсващи текстове", "TaskDownloadMissingLyrics": "Свали липсващи текстове",
"TaskDownloadMissingLyricsDescription": "Свали текстове за песни", "TaskDownloadMissingLyricsDescription": "Свали текстове за песни",
"TaskCleanCollectionsAndPlaylists": "Изчисти колекциите и плейлистите",
"TaskCleanCollectionsAndPlaylistsDescription": "Премахни несъществуващи файлове в колекциите и плейлистите.",
"TaskAudioNormalization": "Нормализиране на звука", "TaskAudioNormalization": "Нормализиране на звука",
"TaskAudioNormalizationDescription": "Сканирай файловете за нормализация на звука.", "TaskAudioNormalizationDescription": "Сканирай файловете за нормализация на звука.",
"TaskExtractMediaSegmentsDescription": "Изважда медиини сегменти от MediaSegment плъгини.", "TaskExtractMediaSegmentsDescription": "Изважда медиини сегменти от MediaSegment плъгини.",

View File

@@ -1,19 +1,31 @@
{ {
"DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
"DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
"Collections": "সংগ্রহশালা", "Collections": "সংগ্রহশালা",
"ChapterNameValue": "অধ্যায় {0}", "ChapterNameValue": "অধ্যায় {0}",
"Channels": "চ্যানেলসমূহ",
"CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",
"Books": "পুস্তকসমূহ", "Books": "পুস্তকসমূহ",
"AuthenticationSucceededWithUserName": "{0} সফলভাবে অথেন্টিকেট করেছেন", "AuthenticationSucceededWithUserName": "{0} সফলভাবে অথেন্টিকেট করেছেন",
"Artists": "শিল্পীগণ", "Artists": "শিল্পীগণ",
"Application": "অ্যাপ্লিকেশন",
"Albums": "অ্যালবামসমূহ",
"HeaderFavoriteEpisodes": "প্রিয় পর্বগুলো", "HeaderFavoriteEpisodes": "প্রিয় পর্বগুলো",
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন", "HeaderContinueWatching": "দেখতে থাকুন",
"HeaderAlbumArtists": "অ্যালবাম শিল্পীবৃন্দ",
"Genres": "ধরণ", "Genres": "ধরণ",
"Folders": "ফোল্ডারসমূহ", "Folders": "ফোল্ডারসমূহ",
"Favorites": "পছন্দসমূহ", "Favorites": "পছন্দসমূহ",
"FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে", "FailedLoginAttemptWithUserName": "{0} লগিন করতে ব্যর্থ হয়েছে",
"AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {1}", "AppDeviceValues": "অ্যাপ: {0}, ডিভাইস: {1}",
"VersionNumber": "সংস্করণ {0}", "VersionNumber": "সংস্করণ {0}",
"ValueSpecialEpisodeName": "বিশেষ পর্ব - {0}",
"ValueHasBeenAddedToLibrary": "আপনার লাইব্রেরিতে {0} যোগ করা হয়েছে",
"UserStoppedPlayingItemWithValues": "{2}তে {1} প্লে শেষ করেছেন {0}", "UserStoppedPlayingItemWithValues": "{2}তে {1} প্লে শেষ করেছেন {0}",
"UserStartedPlayingItemWithValues": "{2}তে {1} প্লে করেছেন {0}", "UserStartedPlayingItemWithValues": "{2}তে {1} প্লে করেছেন {0}",
"UserPolicyUpdatedWithName": "{0} এর জন্য ব্যবহার নীতি আপডেট করা হয়েছে",
"UserPasswordChangedWithName": "ব্যবহারকারী {0} এর পাসওয়ার্ড পরিবর্তিত হয়েছে", "UserPasswordChangedWithName": "ব্যবহারকারী {0} এর পাসওয়ার্ড পরিবর্তিত হয়েছে",
"UserOnlineFromDevice": "{0}, {1} থেকে অনলাইন আছে", "UserOnlineFromDevice": "{0}, {1} থেকে অনলাইন আছে",
"UserOfflineFromDevice": "{0} {1} থেকে বিচ্ছিন্ন হয়ে গেছে", "UserOfflineFromDevice": "{0} {1} থেকে বিচ্ছিন্ন হয়ে গেছে",
@@ -21,14 +33,23 @@
"UserDownloadingItemWithValues": "{0}, {1} ডাউনলোড করছে", "UserDownloadingItemWithValues": "{0}, {1} ডাউনলোড করছে",
"UserDeletedWithName": "ব্যবহারকারী {0}কে বাদ দেয়া হয়েছে", "UserDeletedWithName": "ব্যবহারকারী {0}কে বাদ দেয়া হয়েছে",
"UserCreatedWithName": "ব্যবহারকারী {0} সৃষ্টি করা হয়েছে", "UserCreatedWithName": "ব্যবহারকারী {0} সৃষ্টি করা হয়েছে",
"User": "ব্যবহারকারী",
"TvShows": "টিভি শোগুলো", "TvShows": "টিভি শোগুলো",
"System": "সিস্টেম",
"Sync": "সমন্বয় করুন",
"SubtitleDownloadFailureFromForItem": "{0} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ হয়েছে", "SubtitleDownloadFailureFromForItem": "{0} থেকে {1} এর জন্য সাবটাইটেল ডাউনলোড ব্যর্থ হয়েছে",
"StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।", "StartupEmbyServerIsLoading": "জেলিফিন সার্ভার লোড হচ্ছে। দয়া করে একটু পরে আবার চেষ্টা করুন।",
"Songs": "সঙ্গীত সমূহ",
"Shows": "শো সমূহ", "Shows": "শো সমূহ",
"ServerNameNeedsToBeRestarted": "{0} রিস্টার্ট করা প্রয়োজন",
"ScheduledTaskStartedWithName": "{0} শুরু হয়েছে",
"ScheduledTaskFailedWithName": "{0} ব্যর্থ", "ScheduledTaskFailedWithName": "{0} ব্যর্থ",
"ProviderValue": "প্রদানকারী: {0}",
"PluginUpdatedWithName": "{0} আপডেট করা হয়েছে", "PluginUpdatedWithName": "{0} আপডেট করা হয়েছে",
"PluginUninstalledWithName": "{0} আনইন্সটল হয়েছে", "PluginUninstalledWithName": "{0} আনইন্সটল হয়েছে",
"PluginInstalledWithName": "{0} ইন্সটল হয়েছে", "PluginInstalledWithName": "{0} ইন্সটল হয়েছে",
"Plugin": "প্লাগিন",
"Playlists": "প্লে লিস্ট সমূহ",
"Photos": "ছবিসমূহ", "Photos": "ছবিসমূহ",
"NotificationOptionVideoPlaybackStopped": "ভিডিও প্লেব্যাক বন্ধ হয়েছে", "NotificationOptionVideoPlaybackStopped": "ভিডিও প্লেব্যাক বন্ধ হয়েছে",
"NotificationOptionVideoPlayback": "ভিডিও প্লেব্যাক শুরু হয়েছে", "NotificationOptionVideoPlayback": "ভিডিও প্লেব্যাক শুরু হয়েছে",
@@ -54,13 +75,21 @@
"Music": "গান", "Music": "গান",
"Movies": "চলচ্চিত্রসমূহ", "Movies": "চলচ্চিত্রসমূহ",
"MixedContent": "মিশ্র কন্টেন্ট", "MixedContent": "মিশ্র কন্টেন্ট",
"MessageServerConfigurationUpdated": "সার্ভারের কনফিগারেশন আপডেট করা হয়েছে",
"HeaderRecordingGroups": "রেকর্ডিং গ্রুপগুলো",
"MessageNamedServerConfigurationUpdatedWithValue": "সার্ভার কনফিগারেশন সেকশন {0} আপডেট করা হয়েছে",
"MessageApplicationUpdatedTo": "জেলিফিন সার্ভার {0} তে আপডেট করা হয়েছে",
"MessageApplicationUpdated": "জেলিফিন সার্ভার আপডেট করা হয়েছে",
"Latest": "সর্বশেষ", "Latest": "সর্বশেষ",
"LabelRunningTimeValue": "চলার সময়: {0}", "LabelRunningTimeValue": "চলার সময়: {0}",
"LabelIpAddressValue": "আইপি এড্রেস: {0}", "LabelIpAddressValue": "আইপি এড্রেস: {0}",
"ItemRemovedWithName": "{0} লাইব্রেরি থেকে বাদ দেয়া হয়েছে",
"ItemAddedWithName": "{0} লাইব্রেরিতে যোগ করা হয়েছে",
"Inherit": "উত্তরাধিকারসূত্র থেকে গ্রহণ করুন", "Inherit": "উত্তরাধিকারসূত্র থেকে গ্রহণ করুন",
"HomeVideos": "হোম ভিডিও", "HomeVideos": "হোম ভিডিও",
"HeaderNextUp": "এরপরে আসছে", "HeaderNextUp": "এরপরে আসছে",
"HeaderLiveTV": "লাইভ টিভি", "HeaderLiveTV": "লাইভ টিভি",
"HeaderFavoriteSongs": "প্রিয় গানগুলো",
"HeaderFavoriteShows": "প্রিয় শোগুলো", "HeaderFavoriteShows": "প্রিয় শোগুলো",
"TasksLibraryCategory": "লাইব্রেরি", "TasksLibraryCategory": "লাইব্রেরি",
"TasksMaintenanceCategory": "রক্ষণাবেক্ষণ", "TasksMaintenanceCategory": "রক্ষণাবেক্ষণ",
@@ -98,6 +127,8 @@
"TaskRefreshTrickplayImages": "ট্রিকপ্লে ইমেজ তৈরি", "TaskRefreshTrickplayImages": "ট্রিকপ্লে ইমেজ তৈরি",
"TaskRefreshTrickplayImagesDescription": "সক্ষম লাইব্রেরিতে ভিডিওর জন্য ট্রিকপ্লে প্রিভিউ তৈরি করে।", "TaskRefreshTrickplayImagesDescription": "সক্ষম লাইব্রেরিতে ভিডিওর জন্য ট্রিকপ্লে প্রিভিউ তৈরি করে।",
"TaskDownloadMissingLyricsDescription": "গানের জন্য লিরিকস ডাউনলোড করুন", "TaskDownloadMissingLyricsDescription": "গানের জন্য লিরিকস ডাউনলোড করুন",
"TaskCleanCollectionsAndPlaylists": "কালেকশন এবং প্লেলিস্ট পরিষ্কার করুন",
"TaskCleanCollectionsAndPlaylistsDescription": "কালেকশন এবং প্লেলিস্ট থেকে আইটেমগুলি সরিয়ে দেয় যা আর বিদ্যমান নেই।",
"TaskExtractMediaSegments": "মিডিয়া সেগমেন্ট স্ক্যান", "TaskExtractMediaSegments": "মিডিয়া সেগমেন্ট স্ক্যান",
"TaskExtractMediaSegmentsDescription": "মিডিয়া সেগমেন্ট সক্ষম প্লাগইনগুলি থেকে মিডিয়া সেগমেন্ট বের করে বা অর্জন করে।", "TaskExtractMediaSegmentsDescription": "মিডিয়া সেগমেন্ট সক্ষম প্লাগইনগুলি থেকে মিডিয়া সেগমেন্ট বের করে বা অর্জন করে।",
"TaskDownloadMissingLyrics": "অনুপস্থিত গান ডাউনলোড করুন", "TaskDownloadMissingLyrics": "অনুপস্থিত গান ডাউনলোড করুন",

View File

@@ -1,110 +0,0 @@
{
"Artists": "Umjetnici",
"Books": "Knjige",
"Collections": "Zbirke",
"Default": "Zadano",
"Favorites": "Omiljeni",
"Folders": "Mape",
"Genres": "Žanrovi",
"HeaderContinueWatching": "Nastavi gledati",
"Movies": "Filmovi",
"MusicVideos": "Muzički spotovi",
"Photos": "Slike",
"Shows": "Pokazuje",
"AppDeviceValues": "Aplikacija: {0}, Uređaj: {1}",
"AuthenticationSucceededWithUserName": "{0} uspješno autentificirano",
"ChapterNameValue": "Poglavlje {0}",
"External": "Vanjsko",
"FailedLoginAttemptWithUserName": "Neuspjeli pokušaj prijave sa {0}",
"Forced": "Prisilno",
"HeaderFavoriteEpisodes": "Omiljene epizode",
"HeaderFavoriteShows": "Omiljene emisije",
"HeaderLiveTV": "TV uživo",
"HeaderNextUp": "Slijedi",
"HearingImpaired": "Oštećen sluh",
"HomeVideos": "Kućni videozapisi",
"Inherit": "Nasljedi",
"LabelIpAddressValue": "IP adresa: {0}",
"LabelRunningTimeValue": "Trajanje: {0}",
"Latest": "Posljednje dodano",
"MixedContent": "Miješani sadržaj",
"Music": "Muzika",
"NameInstallFailed": "{0} instalacija je propala",
"NameSeasonNumber": "Sezona {0}",
"NameSeasonUnknown": "Sezona nepoznata",
"NewVersionIsAvailable": "Dostupna je nova verzija Jellyfin Servera za preuzimanje.",
"NotificationOptionApplicationUpdateAvailable": "Dostupno ažuriranje aplikacije",
"NotificationOptionApplicationUpdateInstalled": "Ažuriranje aplikacije instalirano",
"NotificationOptionAudioPlayback": "Pokrenuto je reproduciranje zvuka",
"NotificationOptionAudioPlaybackStopped": "Zaustavljeno je reproduciranje zvuka",
"NotificationOptionCameraImageUploaded": "Učitana slika s kamere",
"NotificationOptionInstallationFailed": "Neuspjeh instalacije",
"NotificationOptionNewLibraryContent": "Dodan novi sadržaj",
"NotificationOptionPluginError": "Neuspjeh dodatka",
"NotificationOptionPluginInstalled": "Dodatak je instaliran",
"NotificationOptionPluginUninstalled": "Dodatak je deinstaliran",
"NotificationOptionPluginUpdateInstalled": "Ažuriranje dodatka je instalirano",
"NotificationOptionServerRestartRequired": "Potreban je ponovni pokret servera",
"NotificationOptionTaskFailed": "Neuspjeh zakazane zadatke",
"NotificationOptionUserLockedOut": "Korisnik je zaključan",
"NotificationOptionVideoPlayback": "Pokrenuto je reproduciranje videa",
"NotificationOptionVideoPlaybackStopped": "Reprodukcija videa je zaustavljena",
"PluginInstalledWithName": "{0} je instaliran",
"PluginUninstalledWithName": "{0} je deinstaliran",
"PluginUpdatedWithName": "{0} je ažurirano",
"ScheduledTaskFailedWithName": "{0} nije uspjelo",
"StartupEmbyServerIsLoading": "Jellyfin Server se učitava. Molimo pokušajte ponovo za kratko vrijeme.",
"SubtitleDownloadFailureFromForItem": "Podtitlovi nisu uspjeli preuzeti sa {0} za {1}",
"TvShows": "TV serije",
"Undefined": "Nedefinirano",
"UserCreatedWithName": "Korisnik {0} je kreiran",
"UserDeletedWithName": "Korisnik {0} je izbrisan",
"UserDownloadingItemWithValues": "{0} preuzima {1}",
"UserLockedOutWithName": "Korisnik {0} je zaključan",
"UserOfflineFromDevice": "{0} se odspojio od {1}",
"UserOnlineFromDevice": "{0} je online od {1}",
"UserPasswordChangedWithName": "Lozinka je promijenjena za korisnika {0}",
"UserStartedPlayingItemWithValues": "{0} igra protiv {1} na {2}",
"UserStoppedPlayingItemWithValues": "{0} je završio igru protiv {1} na {2}",
"VersionNumber": "Verzija {0}",
"TasksMaintenanceCategory": "Održavanje",
"TasksLibraryCategory": "Biblioteka",
"TasksApplicationCategory": "Prijava",
"TasksChannelsCategory": "Internetski kanali",
"TaskCleanActivityLog": "Očisti dnevnik aktivnosti",
"TaskCleanActivityLogDescription": "Brisanje unosa u dnevnik aktivnosti starijih od konfigurisane starosti.",
"TaskCleanCache": "Očistite direktorij keša",
"TaskCleanCacheDescription": "Brisanje keš datoteka koje sistemu više nisu potrebne.",
"TaskRefreshChapterImages": "Izvadi slike iz poglavlja",
"TaskRefreshChapterImagesDescription": "Stvara minijature za videozapise koji imaju poglavlja.",
"TaskAudioNormalization": "Normalizacija zvuka",
"TaskAudioNormalizationDescription": "Skeneriše datoteke radi podataka za normalizaciju zvuka.",
"TaskRefreshLibrary": "Skenerisati medijsku biblioteku",
"TaskRefreshLibraryDescription": "Skenerira vašu medijsku biblioteku na nove datoteke i osvježava metapodatke.",
"TaskCleanLogs": "Očisti direktorij dnevnika",
"TaskCleanLogsDescription": "Brisanje dnevničkih datoteka starijih od {0} dana.",
"TaskRefreshPeople": "Osvježite ljude",
"TaskRefreshPeopleDescription": "Ažurira metapodatke za glumce i režisere u vašoj medijskoj biblioteci.",
"TaskRefreshTrickplayImages": "Generirajte Trickplay slike",
"TaskRefreshTrickplayImagesDescription": "Stvara pregled trik-igara za videozapise u omogućenim bibliotekama.",
"TaskUpdatePlugins": "Ažuriraj dodatke",
"TaskUpdatePluginsDescription": "Preuzima i instalira ažuriranja dodataka koji su konfigurisani da se automatski ažuriraju.",
"TaskCleanTranscode": "Očisti Transcode direktorij",
"TaskCleanTranscodeDescription": "Brisanje transkodiranih datoteka starijih od jednog dana.",
"TaskRefreshChannels": "Osvježi kanale",
"TaskRefreshChannelsDescription": "Osvježava informacije o internetskom kanalu.",
"TaskDownloadMissingLyrics": "Preuzmi nedostajuće tekstove",
"TaskDownloadMissingLyricsDescription": "Preuzmi tekstove pjesama",
"TaskDownloadMissingSubtitles": "Preuzmite nedostajuće titlove",
"TaskDownloadMissingSubtitlesDescription": "Pretražuje internet u potrazi za nedostajućim titlovima na osnovu konfiguracije metapodataka.",
"TaskOptimizeDatabase": "Optimizirajte bazu podataka",
"TaskOptimizeDatabaseDescription": "Komprimira bazu podataka i čisti slobodan prostor. Pokretanje ovog zadatka nakon skeniranja biblioteke ili izvođenja drugih promjena koje podrazumijevaju izmjene baze podataka može poboljšati performanse.",
"TaskKeyframeExtractor": "Izvađač ključnih sličica",
"TaskKeyframeExtractorDescription": "Izvlači ključne okvire iz video datoteka kako bi kreirao preciznije HLS playliste. Ovaj zadatak može trajati dugo.",
"TaskExtractMediaSegments": "Analiza medijskog segmenta",
"TaskExtractMediaSegmentsDescription": "Izvlači ili dobija medijske segmente iz dodataka koji podržavaju MediaSegment.",
"TaskMoveTrickplayImages": "Migracija lokacije slike Trickplay",
"TaskMoveTrickplayImagesDescription": "Premješta postojeće datoteke trik-igara prema postavkama biblioteke.",
"CleanupUserDataTask": "Zadatak čišćenja korisničkih podataka",
"CleanupUserDataTaskDescription": "Čisti sve korisničke podatke (stanje praćenja, status omiljenog itd.) sa medija koji više nije prisutan najmanje 90 dana."
}

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Àlbums",
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}", "AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
"Application": "Aplicació",
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament", "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres", "Books": "Llibres",
"CameraImageUploadedFrom": "S'ha pujat una nova imatge de càmera des de {0}",
"Channels": "Canals",
"ChapterNameValue": "Capítol {0}", "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions", "Collections": "Col·leccions",
"DeviceOfflineWithName": "{0} s'ha desconnectat",
"DeviceOnlineWithName": "{0} està connectat",
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}", "FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
"Favorites": "Preferits", "Favorites": "Preferits",
"Folders": "Directoris", "Folders": "Directoris",
"Genres": "Gèneres", "Genres": "Gèneres",
"HeaderAlbumArtists": "Artistes de l'àlbum",
"HeaderContinueWatching": "Continueu mirant", "HeaderContinueWatching": "Continueu mirant",
"HeaderFavoriteAlbums": "Àlbums preferits",
"HeaderFavoriteArtists": "Artistes preferits",
"HeaderFavoriteEpisodes": "Episodis preferits", "HeaderFavoriteEpisodes": "Episodis preferits",
"HeaderFavoriteShows": "Sèries preferides", "HeaderFavoriteShows": "Sèries preferides",
"HeaderFavoriteSongs": "Cançons preferides",
"HeaderLiveTV": "TV en directe", "HeaderLiveTV": "TV en directe",
"HeaderNextUp": "A continuació", "HeaderNextUp": "A continuació",
"HeaderRecordingGroups": "Grups musicals",
"HomeVideos": "Vídeos domèstics", "HomeVideos": "Vídeos domèstics",
"Inherit": "Heretat", "Inherit": "Heretat",
"ItemAddedWithName": "{0} s'ha afegit a la mediateca",
"ItemRemovedWithName": "{0} s'ha eliminat de la mediateca",
"LabelIpAddressValue": "Adreça IP: {0}", "LabelIpAddressValue": "Adreça IP: {0}",
"LabelRunningTimeValue": "Temps en marxa: {0}", "LabelRunningTimeValue": "Temps en marxa: {0}",
"Latest": "Darrers", "Latest": "Darrers",
"MessageApplicationUpdated": "El servidor de Jellyfin ha estat actualitzat",
"MessageApplicationUpdatedTo": "El servidor de Jellyfin ha estat actualitzat a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
"MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
"MixedContent": "Contingut barrejat", "MixedContent": "Contingut barrejat",
"Movies": "Pel·lícules", "Movies": "Pel·lícules",
"Music": "Música", "Music": "Música",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Reproducció de vídeo iniciada", "NotificationOptionVideoPlayback": "Reproducció de vídeo iniciada",
"NotificationOptionVideoPlaybackStopped": "Reproducció de vídeo aturada", "NotificationOptionVideoPlaybackStopped": "Reproducció de vídeo aturada",
"Photos": "Fotos", "Photos": "Fotos",
"PluginInstalledWithName": "S'ha instal·lat {0}", "Playlists": "Llistes de reproducció",
"PluginUninstalledWithName": "S'ha desinstal·lat {0}", "Plugin": "Complement",
"PluginInstalledWithName": "{0} ha estat instal·lat",
"PluginUninstalledWithName": "S'ha instal·lat {0}",
"PluginUpdatedWithName": "S'ha actualitzat {0}", "PluginUpdatedWithName": "S'ha actualitzat {0}",
"ProviderValue": "Proveïdor: {0}",
"ScheduledTaskFailedWithName": "{0} ha fallat", "ScheduledTaskFailedWithName": "{0} ha fallat",
"ScheduledTaskStartedWithName": "S'ha iniciat {0}",
"ServerNameNeedsToBeRestarted": "S'ha de reiniciar {0}",
"Shows": "Sèries", "Shows": "Sèries",
"Songs": "Cançons",
"StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu-ho de nou en una estona.", "StartupEmbyServerIsLoading": "El servidor de Jellyfin s'està carregant. Proveu-ho de nou en una estona.",
"SubtitleDownloadFailureFromForItem": "Els subtítols per a {1} no s'han pogut baixar de {0}", "SubtitleDownloadFailureFromForItem": "Els subtítols per a {1} no s'han pogut baixar de {0}",
"Sync": "Sincronitza",
"System": "Sistema",
"TvShows": "Sèries de TV", "TvShows": "Sèries de TV",
"User": "Usuari",
"UserCreatedWithName": "S'ha creat l'usuari {0}", "UserCreatedWithName": "S'ha creat l'usuari {0}",
"UserDeletedWithName": "S'ha eliminat l'usuari {0}", "UserDeletedWithName": "S'ha eliminat l'usuari {0}",
"UserDownloadingItemWithValues": "{0} està descarregant {1}", "UserDownloadingItemWithValues": "{0} està descarregant {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} s'ha desconnectat de {1}", "UserOfflineFromDevice": "{0} s'ha desconnectat de {1}",
"UserOnlineFromDevice": "{0} està connectat des de {1}", "UserOnlineFromDevice": "{0} està connectat des de {1}",
"UserPasswordChangedWithName": "S'ha canviat la contrasenya per a l'usuari {0}", "UserPasswordChangedWithName": "S'ha canviat la contrasenya per a l'usuari {0}",
"UserPolicyUpdatedWithName": "La política d'usuari s'ha actualitzat per a {0}",
"UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1} a {2}", "UserStartedPlayingItemWithValues": "{0} ha començat a reproduir {1} a {2}",
"UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1} a {2}", "UserStoppedPlayingItemWithValues": "{0} ha parat de reproduir {1} a {2}",
"ValueHasBeenAddedToLibrary": "S'ha afegit {0} a la mediateca",
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versió {0}", "VersionNumber": "Versió {0}",
"TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.", "TaskDownloadMissingSubtitlesDescription": "Cerca a internet els subtítols que faltin a partir de la configuració de metadades.",
"TaskDownloadMissingSubtitles": "Descàrrega dels subtítols que faltin", "TaskDownloadMissingSubtitles": "Descàrrega dels subtítols que faltin",
@@ -75,7 +104,7 @@
"TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.", "TaskCleanLogsDescription": "Esborra els registres que tinguin més de {0} dies.",
"TaskCleanLogs": "Neteja dels registres", "TaskCleanLogs": "Neteja dels registres",
"TaskRefreshLibraryDescription": "Escaneja les mediateques, a la cerca de fitxers nous i refresca les metadades.", "TaskRefreshLibraryDescription": "Escaneja les mediateques, a la cerca de fitxers nous i refresca les metadades.",
"TaskRefreshLibrary": "Escaneja la mediateca", "TaskRefreshLibrary": "Escaneig de les mediateques",
"TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.", "TaskRefreshChapterImagesDescription": "Creació de les miniatures dels vídeos que tinguin capítols.",
"TaskRefreshChapterImages": "Extracció de les imatges dels capítols", "TaskRefreshChapterImages": "Extracció de les imatges dels capítols",
"TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.", "TaskCleanCacheDescription": "Eliminació de la memòria cau no necessària per al servidor.",
@@ -97,6 +126,8 @@
"HearingImpaired": "Discapacitat auditiva", "HearingImpaired": "Discapacitat auditiva",
"TaskRefreshTrickplayImages": "Generació d'imatges de previsualització", "TaskRefreshTrickplayImages": "Generació d'imatges de previsualització",
"TaskRefreshTrickplayImagesDescription": "Creació d'imatges de previsualització per a vídeos en les mediateques habilitades.", "TaskRefreshTrickplayImagesDescription": "Creació d'imatges de previsualització per a vídeos en les mediateques habilitades.",
"TaskCleanCollectionsAndPlaylistsDescription": "Esborra elements de col·leccions i llistes de reproducció que ja no existeixen.",
"TaskCleanCollectionsAndPlaylists": "Neteja de les col·leccions i llistes de reproducció",
"TaskAudioNormalization": "Estabilització de l'àudio", "TaskAudioNormalization": "Estabilització de l'àudio",
"TaskAudioNormalizationDescription": "Escaneja els fitxer per a obtenir dades de normalització de l'àudio.", "TaskAudioNormalizationDescription": "Escaneja els fitxer per a obtenir dades de normalització de l'àudio.",
"TaskDownloadMissingLyricsDescription": "Descàrrega de les lletres de les cançons", "TaskDownloadMissingLyricsDescription": "Descàrrega de les lletres de les cançons",
@@ -106,6 +137,5 @@
"TaskMoveTrickplayImages": "Migració de la ubicació de la imatge de previsualització", "TaskMoveTrickplayImages": "Migració de la ubicació de la imatge de previsualització",
"TaskMoveTrickplayImagesDescription": "Mou els fitxers existents d'imatges de previsualització segons la configuració de la mediateca.", "TaskMoveTrickplayImagesDescription": "Mou els fitxers existents d'imatges de previsualització segons la configuració de la mediateca.",
"CleanupUserDataTaskDescription": "Neteja totes les dades d'usuari (estat de la visualització, estat dels preferits, etc.) del contingut multimèdia que no ha estat present durant almenys 90 dies.", "CleanupUserDataTaskDescription": "Neteja totes les dades d'usuari (estat de la visualització, estat dels preferits, etc.) del contingut multimèdia que no ha estat present durant almenys 90 dies.",
"CleanupUserDataTask": "Tasca de neteja de dades d'usuari", "CleanupUserDataTask": "Tasca de neteja de dades d'usuari"
"Original": "Original"
} }

View File

@@ -1,30 +1,44 @@
{ {
"ChapterNameValue": "Didanedi {0}", "ChapterNameValue": "Didanedi {0}",
"HeaderAlbumArtists": "Didanidanolisgisgi",
"HeaderFavoriteAlbums": "Dvganidi didanidisgisgi",
"HeaderLiveTV": "Anigadi didanidisgosgi", "HeaderLiveTV": "Anigadi didanidisgosgi",
"HeaderRecordingGroups": "Didanisquodiisgisgi",
"HomeVideos": "Diganadi dinagadisgisgi", "HomeVideos": "Diganadi dinagadisgisgi",
"Inherit": "Anigwe", "Inherit": "Anigwe",
"MessageApplicationUpdatedTo": "Tsenigwidinonvhi Jellyfin Server tsadanidigwe anigadi {0}",
"MixedContent": "Ganinidi dininoladisgisgi", "MixedContent": "Ganinidi dininoladisgisgi",
"Movies": "Anidvnisgisgi", "Movies": "Anidvnisgisgi",
"MusicVideos": "Danodisgisgi didanidisgosgi", "MusicVideos": "Danodisgisgi didanidisgosgi",
"NotificationOptionAudioPlayback": "Didanidigwe diganuyisgisgi anigadi", "NotificationOptionAudioPlayback": "Didanidigwe diganuyisgisgi anigadi",
"NotificationOptionInstallationFailed": "Diudvdi anadvnatisgisgi", "NotificationOptionInstallationFailed": "Diudvdi anadvnatisgisgi",
"NotificationOptionPluginUninstalled": "Ditsigvhnidv anawvdisgisgi", "NotificationOptionPluginUninstalled": "Ditsigvhnidv anawvdisgisgi",
"Albums": "Anigawidaniyv",
"Application": "Didanvyi",
"Artists": "Dinidaniyi", "Artists": "Dinidaniyi",
"AuthenticationSucceededWithUserName": "{0} Sesoquonisdi nagadani", "AuthenticationSucceededWithUserName": "{0} Sesoquonisdi nagadani",
"Books": "Didanedi", "Books": "Didanedi",
"CameraImageUploadedFrom": "Anigawidaniyv nasgi didagwalanvyi {0}",
"Channels": "Diganadasgi",
"Collections": "Diganadisgi", "Collections": "Diganadisgi",
"Default": "Dinadi", "Default": "Dinadi",
"DeviceOfflineWithName": "{0} Aniyvolehvi nasgi",
"External": "Amohdi", "External": "Amohdi",
"Favorites": "Nvdayelvdisgi", "Favorites": "Nvdayelvdisgi",
"Folders": "Didanididisgi", "Folders": "Didanididisgi",
"Forced": "Ganedi", "Forced": "Ganedi",
"Genres": "Diganadisgi", "Genres": "Diganadisgi",
"HeaderContinueWatching": "Uwoditsu asdanidisgisgi", "HeaderContinueWatching": "Uwoditsu asdanidisgisgi",
"HeaderFavoriteArtists": "Dvganidi dinidanolisgisgi",
"HeaderFavoriteEpisodes": "Dvganidi didanidilisgadisgisgi", "HeaderFavoriteEpisodes": "Dvganidi didanidilisgadisgisgi",
"HeaderFavoriteShows": "Dvganidi didanididanolisgisgi)", "HeaderFavoriteShows": "Dvganidi didanididanolisgisgi)",
"HeaderFavoriteSongs": "Dvganidi danodisgisgi",
"HeaderNextUp": "Anidvli uwodoli", "HeaderNextUp": "Anidvli uwodoli",
"HearingImpaired": "Anitsunidi talunidisgisgi", "HearingImpaired": "Anitsunidi talunidisgisgi",
"ItemAddedWithName": "{0} Dinigwe anididanidisgi",
"Latest": "Uwodoli", "Latest": "Uwodoli",
"MessageApplicationUpdated": "Tsenigwidinonvhi Jellyfin Server tsadanidigwe",
"MessageServerConfigurationUpdated": "Sedanidvdi anigadi diganidinonvhi",
"Music": "Danodisgisgi", "Music": "Danodisgisgi",
"NameSeasonUnknown": "Tsunita anidvdisgi", "NameSeasonUnknown": "Tsunita anidvdisgi",
"NewVersionIsAvailable": "Danodigwe anigadi Jellyfin Server tsadanidigwe adisdi uwodvdi diganidinonvhi.", "NewVersionIsAvailable": "Danodigwe anigadi Jellyfin Server tsadanidigwe adisdi uwodvdi diganidinonvhi.",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Alba",
"AppDeviceValues": "Aplikace: {0}, Zařízení: {1}", "AppDeviceValues": "Aplikace: {0}, Zařízení: {1}",
"Application": "Aplikace",
"Artists": "Umělci", "Artists": "Umělci",
"AuthenticationSucceededWithUserName": "{0} úspěšně ověřen", "AuthenticationSucceededWithUserName": "{0} úspěšně ověřen",
"Books": "Knihy", "Books": "Knihy",
"CameraImageUploadedFrom": "Z {0} byla nahrána nová fotografie z fotoaparátu",
"Channels": "Kanály",
"ChapterNameValue": "Kapitola {0}", "ChapterNameValue": "Kapitola {0}",
"Collections": "Kolekce", "Collections": "Kolekce",
"DeviceOfflineWithName": "{0} se odpojil",
"DeviceOnlineWithName": "{0} je připojen",
"FailedLoginAttemptWithUserName": "Neúspěšný pokus o přihlášení z {0}", "FailedLoginAttemptWithUserName": "Neúspěšný pokus o přihlášení z {0}",
"Favorites": "Oblíbené", "Favorites": "Oblíbené",
"Folders": "Složky", "Folders": "Složky",
"Genres": "Žánry", "Genres": "Žánry",
"HeaderAlbumArtists": "Umělci alba",
"HeaderContinueWatching": "Pokračovat ve sledování", "HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti",
"HeaderFavoriteEpisodes": "Oblíbené epizody", "HeaderFavoriteEpisodes": "Oblíbené epizody",
"HeaderFavoriteShows": "Oblíbené seriály", "HeaderFavoriteShows": "Oblíbené seriály",
"HeaderFavoriteSongs": "Oblíbená hudba",
"HeaderLiveTV": "TV vysílání", "HeaderLiveTV": "TV vysílání",
"HeaderNextUp": "Další díly", "HeaderNextUp": "Další díly",
"HeaderRecordingGroups": "Skupiny nahrávek",
"HomeVideos": "Domácí videa", "HomeVideos": "Domácí videa",
"Inherit": "Zdědit", "Inherit": "Zdědit",
"ItemAddedWithName": "{0} byl přidán do knihovny",
"ItemRemovedWithName": "{0} byl odstraněn z knihovny",
"LabelIpAddressValue": "IP adresa: {0}", "LabelIpAddressValue": "IP adresa: {0}",
"LabelRunningTimeValue": "Délka média: {0}", "LabelRunningTimeValue": "Délka média: {0}",
"Latest": "Nejnovější", "Latest": "Nejnovější",
"MessageApplicationUpdated": "Jellyfin Server byl aktualizován",
"MessageApplicationUpdatedTo": "Jellyfin server byl aktualizován na verzi {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurace sekce {0} na serveru byla aktualizována",
"MessageServerConfigurationUpdated": "Konfigurace serveru aktualizována",
"MixedContent": "Smíšený obsah", "MixedContent": "Smíšený obsah",
"Movies": "Filmy", "Movies": "Filmy",
"Music": "Hudba", "Music": "Hudba",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Přehrávání videa zahájeno", "NotificationOptionVideoPlayback": "Přehrávání videa zahájeno",
"NotificationOptionVideoPlaybackStopped": "Přehrávání videa ukončeno", "NotificationOptionVideoPlaybackStopped": "Přehrávání videa ukončeno",
"Photos": "Fotky", "Photos": "Fotky",
"Playlists": "Seznamy skladeb",
"Plugin": "Zásuvný modul",
"PluginInstalledWithName": "{0} byl nainstalován", "PluginInstalledWithName": "{0} byl nainstalován",
"PluginUninstalledWithName": "{0} byl odinstalován", "PluginUninstalledWithName": "{0} byl odinstalován",
"PluginUpdatedWithName": "{0} byl aktualizován", "PluginUpdatedWithName": "{0} byl aktualizován",
"ProviderValue": "Poskytl: {0}",
"ScheduledTaskFailedWithName": "{0} selhalo", "ScheduledTaskFailedWithName": "{0} selhalo",
"ScheduledTaskStartedWithName": "{0} zahájeno",
"ServerNameNeedsToBeRestarted": "{0} vyžaduje restart",
"Shows": "Seriály", "Shows": "Seriály",
"Songs": "Skladby",
"StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.", "StartupEmbyServerIsLoading": "Jellyfin Server je spouštěn. Zkuste to prosím v brzké době znovu.",
"SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo", "SubtitleDownloadFailureFromForItem": "Stažení titulků pro {1} z {0} selhalo",
"Sync": "Synchronizace",
"System": "Systém",
"TvShows": "Seriály", "TvShows": "Seriály",
"User": "Uživatel",
"UserCreatedWithName": "Uživatel {0} byl vytvořen", "UserCreatedWithName": "Uživatel {0} byl vytvořen",
"UserDeletedWithName": "Uživatel {0} byl smazán", "UserDeletedWithName": "Uživatel {0} byl smazán",
"UserDownloadingItemWithValues": "{0} stahuje {1}", "UserDownloadingItemWithValues": "{0} stahuje {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} se odpojil ze zařízení {1}", "UserOfflineFromDevice": "{0} se odpojil ze zařízení {1}",
"UserOnlineFromDevice": "{0} se připojil ze zařízení {1}", "UserOnlineFromDevice": "{0} se připojil ze zařízení {1}",
"UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}", "UserPasswordChangedWithName": "Provedena změna hesla pro uživatele {0}",
"UserPolicyUpdatedWithName": "Zásady uživatele pro {0} byly aktualizovány",
"UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}", "UserStartedPlayingItemWithValues": "{0} spustil přehrávání {1}",
"UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}", "UserStoppedPlayingItemWithValues": "{0} zastavil přehrávání {1}",
"ValueHasBeenAddedToLibrary": "{0} byl přidán do vaší knihovny médií",
"ValueSpecialEpisodeName": "Speciál - {0}",
"VersionNumber": "Verze {0}", "VersionNumber": "Verze {0}",
"TaskDownloadMissingSubtitlesDescription": "Vyhledá na internetu chybějící titulky na základě nastavení metadat.", "TaskDownloadMissingSubtitlesDescription": "Vyhledá na internetu chybějící titulky na základě nastavení metadat.",
"TaskDownloadMissingSubtitles": "Stáhnout chybějící titulky", "TaskDownloadMissingSubtitles": "Stáhnout chybějící titulky",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Normalizace zvuku",
"TaskAudioNormalizationDescription": "Skenovat soubory za účelem normalizace zvuku.", "TaskAudioNormalizationDescription": "Skenovat soubory za účelem normalizace zvuku.",
"TaskDownloadMissingLyrics": "Stáhnout chybějící texty k písni", "TaskDownloadMissingLyrics": "Stáhnout chybějící texty k písni",
@@ -106,6 +137,5 @@
"TaskMoveTrickplayImages": "Přesunout úložiště obrázků Trickplay", "TaskMoveTrickplayImages": "Přesunout úložiště obrázků Trickplay",
"TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny.", "TaskMoveTrickplayImagesDescription": "Přesune existující soubory Trickplay podle nastavení knihovny.",
"CleanupUserDataTaskDescription": "Odstraní všechna uživatelská data (stav zhlédnutí, oblíbené atd.) z médií, které již neexistují více než 90 dní.", "CleanupUserDataTaskDescription": "Odstraní všechna uživatelská data (stav zhlédnutí, oblíbené atd.) z médií, které již neexistují více než 90 dní.",
"CleanupUserDataTask": "Pročistit uživatelská data", "CleanupUserDataTask": "Pročistit uživatelská data"
"Original": "Originál"
} }

View File

@@ -1,11 +1,16 @@
{ {
"DeviceOnlineWithName": "Mae {0} wedi'i gysylltu",
"DeviceOfflineWithName": "Mae {0} wedi datgysylltu",
"Default": "Diofyn", "Default": "Diofyn",
"Collections": "Casgliadau", "Collections": "Casgliadau",
"ChapterNameValue": "Pennod {0}", "ChapterNameValue": "Pennod {0}",
"Channels": "Sianeli",
"CameraImageUploadedFrom": "Mae delwedd camera newydd wedi'i lanlwytho o {0}",
"Books": "Llyfrau", "Books": "Llyfrau",
"AuthenticationSucceededWithUserName": "{0} wedii ddilysun llwyddiannus", "AuthenticationSucceededWithUserName": "{0} wedii ddilysun llwyddiannus",
"Artists": "Crewyr", "Artists": "Crewyr",
"AppDeviceValues": "Ap: {0}, Dyfais: {1}", "AppDeviceValues": "Ap: {0}, Dyfais: {1}",
"Albums": "Albwmau",
"Genres": "Genres", "Genres": "Genres",
"Folders": "Ffolderi", "Folders": "Ffolderi",
"Favorites": "Ffefrynnau", "Favorites": "Ffefrynnau",
@@ -15,7 +20,9 @@
"TaskRefreshPeople": "Adnewyddu Pobl", "TaskRefreshPeople": "Adnewyddu Pobl",
"TasksChannelsCategory": "Sianeli Internet", "TasksChannelsCategory": "Sianeli Internet",
"VersionNumber": "Fersiwn {0}", "VersionNumber": "Fersiwn {0}",
"ScheduledTaskStartedWithName": "{0} wedi dechrau",
"ScheduledTaskFailedWithName": "{0} wedi methu", "ScheduledTaskFailedWithName": "{0} wedi methu",
"ProviderValue": "Darparwr: {0}",
"NotificationOptionInstallationFailed": "Fethu Gosod", "NotificationOptionInstallationFailed": "Fethu Gosod",
"NameSeasonUnknown": "Tymor Anhysbys", "NameSeasonUnknown": "Tymor Anhysbys",
"NameSeasonNumber": "Tymor {0}", "NameSeasonNumber": "Tymor {0}",
@@ -23,20 +30,31 @@
"MixedContent": "Cynnwys amrywiol", "MixedContent": "Cynnwys amrywiol",
"HomeVideos": "Genres", "HomeVideos": "Genres",
"HeaderNextUp": "Nesaf i Fyny", "HeaderNextUp": "Nesaf i Fyny",
"HeaderFavoriteArtists": "Ffefryn Artistiaid",
"HeaderFavoriteAlbums": "Ffefryn Albwmau",
"HeaderContinueWatching": "Parhewch i Wylio", "HeaderContinueWatching": "Parhewch i Wylio",
"TasksApplicationCategory": "Rhaglen", "TasksApplicationCategory": "Rhaglen",
"TasksLibraryCategory": "Llyfrgell", "TasksLibraryCategory": "Llyfrgell",
"TasksMaintenanceCategory": "Cynnal a Chadw", "TasksMaintenanceCategory": "Cynnal a Chadw",
"System": "System",
"Plugin": "Ategyn",
"Music": "Cerddoriaeth", "Music": "Cerddoriaeth",
"Latest": "Diweddaraf", "Latest": "Diweddaraf",
"Inherit": "Etifeddu", "Inherit": "Etifeddu",
"Forced": "Orfodi", "Forced": "Orfodi",
"Application": "Rhaglen",
"HeaderAlbumArtists": "Artistiaid albwm",
"Sync": "Cysoni",
"Songs": "Caneuon",
"Shows": "Rhaglenni", "Shows": "Rhaglenni",
"Playlists": "Rhestri Chwarae",
"Photos": "Lluniau", "Photos": "Lluniau",
"ValueSpecialEpisodeName": "Arbennig - {0}",
"Movies": "Ffilmiau", "Movies": "Ffilmiau",
"Undefined": "Heb ddiffiniad", "Undefined": "Heb ddiffiniad",
"TvShows": "Rhaglenni teledu", "TvShows": "Rhaglenni teledu",
"HeaderLiveTV": "Teledu Byw", "HeaderLiveTV": "Teledu Byw",
"User": "Defnyddiwr",
"TaskCleanLogsDescription": "Dileu ffeiliau log sy'n fwy na {0} diwrnod oed.", "TaskCleanLogsDescription": "Dileu ffeiliau log sy'n fwy na {0} diwrnod oed.",
"TaskCleanLogs": "Glanhau ffolder log", "TaskCleanLogs": "Glanhau ffolder log",
"TaskRefreshLibraryDescription": "Sganio'ch llyfrgell gyfryngau am ffeiliau newydd ac yn adnewyddu metaddata.", "TaskRefreshLibraryDescription": "Sganio'ch llyfrgell gyfryngau am ffeiliau newydd ac yn adnewyddu metaddata.",
@@ -47,9 +65,13 @@
"NotificationOptionPluginError": "Methodd ategyn", "NotificationOptionPluginError": "Methodd ategyn",
"NotificationOptionAudioPlaybackStopped": "Stopiwyd chwarae sain", "NotificationOptionAudioPlaybackStopped": "Stopiwyd chwarae sain",
"NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain", "NotificationOptionAudioPlayback": "Dechreuwyd chwarae sain",
"MessageServerConfigurationUpdated": "Mae gosodiadau gweinydd wedi'i ddiweddaru",
"MessageNamedServerConfigurationUpdatedWithValue": "Mae adran gosodiadau gweinydd {0} wedi'i diweddaru",
"FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu o {0}", "FailedLoginAttemptWithUserName": "Cais mewngofnodi wedi methu o {0}",
"ValueHasBeenAddedToLibrary": "{0} wedi'i hychwanegu at eich llyfrgell gyfryngau",
"UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}", "UserStoppedPlayingItemWithValues": "{0} wedi gorffen chwarae {1} ar {2}",
"UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}", "UserStartedPlayingItemWithValues": "{0} yn chwarae {1} ar {2}",
"UserPolicyUpdatedWithName": "Polisi defnyddiwr wedi'i newid ar gyfer {0}",
"UserPasswordChangedWithName": "Cyfrinair wedi'i newid ar gyfer defnyddiwr {0}", "UserPasswordChangedWithName": "Cyfrinair wedi'i newid ar gyfer defnyddiwr {0}",
"UserOnlineFromDevice": "Mae {0} ar-lein o {1}", "UserOnlineFromDevice": "Mae {0} ar-lein o {1}",
"UserOfflineFromDevice": "Mae {0} wedi datgysylltu o {1}", "UserOfflineFromDevice": "Mae {0} wedi datgysylltu o {1}",
@@ -58,6 +80,7 @@
"UserDeletedWithName": "Defnyddiwr {0} wedi'i ddileu", "UserDeletedWithName": "Defnyddiwr {0} wedi'i ddileu",
"UserCreatedWithName": "Defnyddiwr {0} wedi'i greu", "UserCreatedWithName": "Defnyddiwr {0} wedi'i greu",
"StartupEmbyServerIsLoading": "Gweinydd Jellyfin yn llwytho. Triwch eto mewn ychydig.", "StartupEmbyServerIsLoading": "Gweinydd Jellyfin yn llwytho. Triwch eto mewn ychydig.",
"ServerNameNeedsToBeRestarted": "Mae angen ailddechrau {0}",
"PluginUpdatedWithName": "{0} wedi'i ddiweddaru", "PluginUpdatedWithName": "{0} wedi'i ddiweddaru",
"PluginUninstalledWithName": "{0} wedi'i ddadosod", "PluginUninstalledWithName": "{0} wedi'i ddadosod",
"PluginInstalledWithName": "{0} wedi'i osod", "PluginInstalledWithName": "{0} wedi'i osod",
@@ -75,7 +98,13 @@
"NotificationOptionApplicationUpdateAvailable": "Diweddariad ap ar gael", "NotificationOptionApplicationUpdateAvailable": "Diweddariad ap ar gael",
"NewVersionIsAvailable": "Mae fersiwn diweddarach o'r gweinydd Jellyfin ar gael.", "NewVersionIsAvailable": "Mae fersiwn diweddarach o'r gweinydd Jellyfin ar gael.",
"NameInstallFailed": "Gosodiad {0} wedi methu", "NameInstallFailed": "Gosodiad {0} wedi methu",
"MessageApplicationUpdatedTo": "Gweinydd Jellyfin wedi'i ddiweddaru i {0}",
"MessageApplicationUpdated": "Gweinydd Jellyfin wedi'i ddiweddaru",
"LabelIpAddressValue": "Cyfeiriad IP: {0}", "LabelIpAddressValue": "Cyfeiriad IP: {0}",
"ItemRemovedWithName": "{0} wedi'i dynnu o'r llyfrgell",
"ItemAddedWithName": "{0} wedi'i adio i'r llyfrgell",
"HeaderRecordingGroups": "Grwpiau Recordio",
"HeaderFavoriteSongs": "Ffefryn Ganeuon",
"HeaderFavoriteShows": "Ffefryn Shoeau", "HeaderFavoriteShows": "Ffefryn Shoeau",
"HeaderFavoriteEpisodes": "Ffefryn Rhaglenni", "HeaderFavoriteEpisodes": "Ffefryn Rhaglenni",
"TaskDownloadMissingSubtitlesDescription": "Chwilio'r rhyngrwyd am is-deitlau coll yn seiliedig ar gosodiadau metaddata.", "TaskDownloadMissingSubtitlesDescription": "Chwilio'r rhyngrwyd am is-deitlau coll yn seiliedig ar gosodiadau metaddata.",
@@ -101,5 +130,7 @@
"TaskRefreshTrickplayImagesDescription": "Creu rhagolygon Trickplay ar gyfer fideos mewn llyfrgelloedd gweithredol.", "TaskRefreshTrickplayImagesDescription": "Creu rhagolygon Trickplay ar gyfer fideos mewn llyfrgelloedd gweithredol.",
"TaskDownloadMissingLyrics": "Lawrlwytho geiriau coll", "TaskDownloadMissingLyrics": "Lawrlwytho geiriau coll",
"TaskDownloadMissingLyricsDescription": "Lawrlwytho geiriau caneuon", "TaskDownloadMissingLyricsDescription": "Lawrlwytho geiriau caneuon",
"TaskCleanCollectionsAndPlaylists": "Glanhau casgliadau a rhestrau chwarae",
"TaskCleanCollectionsAndPlaylistsDescription": "Dileu eitemau o gasgliadau a rhestrau chwarae sydd ddim yn bodoli bellach.",
"TaskExtractMediaSegments": "Sganio Darnau Cyfryngau" "TaskExtractMediaSegments": "Sganio Darnau Cyfryngau"
} }

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Albummer",
"AppDeviceValues": "App: {0}, Enhed: {1}", "AppDeviceValues": "App: {0}, Enhed: {1}",
"Application": "Applikation",
"Artists": "Kunstnere", "Artists": "Kunstnere",
"AuthenticationSucceededWithUserName": "{0} er logget ind", "AuthenticationSucceededWithUserName": "{0} er logget ind",
"Books": "Bøger", "Books": "Bøger",
"CameraImageUploadedFrom": "Et nyt kamerabillede er blevet uploadet fra {0}",
"Channels": "Kanaler",
"ChapterNameValue": "Kapitel {0}", "ChapterNameValue": "Kapitel {0}",
"Collections": "Samlinger", "Collections": "Samlinger",
"DeviceOfflineWithName": "{0} har afbrudt forbindelsen",
"DeviceOnlineWithName": "{0} er forbundet",
"FailedLoginAttemptWithUserName": "Mislykket loginforsøg fra {0}", "FailedLoginAttemptWithUserName": "Mislykket loginforsøg fra {0}",
"Favorites": "Favoritter", "Favorites": "Favoritter",
"Folders": "Mapper", "Folders": "Mapper",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumkunstnere",
"HeaderContinueWatching": "Fortsæt afspilning", "HeaderContinueWatching": "Fortsæt afspilning",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritkunstnere",
"HeaderFavoriteEpisodes": "Yndlingsafsnit", "HeaderFavoriteEpisodes": "Yndlingsafsnit",
"HeaderFavoriteShows": "Yndlingsserier", "HeaderFavoriteShows": "Yndlingsserier",
"HeaderFavoriteSongs": "Yndlingssange",
"HeaderLiveTV": "Live-TV", "HeaderLiveTV": "Live-TV",
"HeaderNextUp": "Næste", "HeaderNextUp": "Næste",
"HeaderRecordingGroups": "Optagelsesgrupper",
"HomeVideos": "Hjemmevideoer", "HomeVideos": "Hjemmevideoer",
"Inherit": "Nedarv", "Inherit": "Nedarv",
"ItemAddedWithName": "{0} blev tilføjet til biblioteket",
"ItemRemovedWithName": "{0} blev fjernet fra biblioteket",
"LabelIpAddressValue": "IP-adresse: {0}", "LabelIpAddressValue": "IP-adresse: {0}",
"LabelRunningTimeValue": "Spilletid: {0}", "LabelRunningTimeValue": "Spilletid: {0}",
"Latest": "Seneste", "Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfiguration sektion {0} er blevet opdateret",
"MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold", "MixedContent": "Blandet indhold",
"Movies": "Film", "Movies": "Film",
"Music": "Musik", "Music": "Musik",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Videoafspilning påbegyndt", "NotificationOptionVideoPlayback": "Videoafspilning påbegyndt",
"NotificationOptionVideoPlaybackStopped": "Videoafspilning blev stoppet", "NotificationOptionVideoPlaybackStopped": "Videoafspilning blev stoppet",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Afspilningslister",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} blev installeret", "PluginInstalledWithName": "{0} blev installeret",
"PluginUninstalledWithName": "{0} blev afinstalleret", "PluginUninstalledWithName": "{0} blev afinstalleret",
"PluginUpdatedWithName": "{0} blev opdateret", "PluginUpdatedWithName": "{0} blev opdateret",
"ProviderValue": "Udbyder: {0}",
"ScheduledTaskFailedWithName": "{0} mislykkedes", "ScheduledTaskFailedWithName": "{0} mislykkedes",
"ScheduledTaskStartedWithName": "{0} påbegyndte",
"ServerNameNeedsToBeRestarted": "{0} skal genstartes",
"Shows": "Serier", "Shows": "Serier",
"Songs": "Sange",
"StartupEmbyServerIsLoading": "Jellyfin er i gang med at starte. Prøv igen om et øjeblik.", "StartupEmbyServerIsLoading": "Jellyfin er i gang med at starte. Prøv igen om et øjeblik.",
"SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke hentes fra {0} til {1}", "SubtitleDownloadFailureFromForItem": "Undertekster kunne ikke hentes fra {0} til {1}",
"Sync": "Synkroniser",
"System": "System",
"TvShows": "TV-serier", "TvShows": "TV-serier",
"User": "Bruger",
"UserCreatedWithName": "Bruger {0} er blevet oprettet", "UserCreatedWithName": "Bruger {0} er blevet oprettet",
"UserDeletedWithName": "Brugeren {0} er nu slettet", "UserDeletedWithName": "Brugeren {0} er nu slettet",
"UserDownloadingItemWithValues": "{0} henter {1}", "UserDownloadingItemWithValues": "{0} henter {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} har afbrudt fra {1}", "UserOfflineFromDevice": "{0} har afbrudt fra {1}",
"UserOnlineFromDevice": "{0} er online fra {1}", "UserOnlineFromDevice": "{0} er online fra {1}",
"UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}", "UserPasswordChangedWithName": "Adgangskode er ændret for brugeren {0}",
"UserPolicyUpdatedWithName": "Brugerpolitikken er blevet opdateret for {0}",
"UserStartedPlayingItemWithValues": "{0} afspiller {1} på {2}", "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",
"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",
@@ -97,6 +126,8 @@
"HearingImpaired": "Hørehæmmet", "HearingImpaired": "Hørehæmmet",
"TaskRefreshTrickplayImages": "Generer trickplay-billeder", "TaskRefreshTrickplayImages": "Generer trickplay-billeder",
"TaskRefreshTrickplayImagesDescription": "Laver trickplay-billeder for videoer i aktiverede biblioteker.", "TaskRefreshTrickplayImagesDescription": "Laver trickplay-billeder 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 lydnormalisering.", "TaskAudioNormalizationDescription": "Skanner filer for data vedrørende lydnormalisering.",
"TaskAudioNormalization": "Lydnormalisering", "TaskAudioNormalization": "Lydnormalisering",
"TaskDownloadMissingLyricsDescription": "Søger på internettet efter manglende sangtekster baseret på metadata-konfigurationen", "TaskDownloadMissingLyricsDescription": "Søger på internettet efter manglende sangtekster baseret på metadata-konfigurationen",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Alben",
"AppDeviceValues": "App: {0}, Gerät: {1}", "AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten", "Artists": "Interpreten",
"AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert", "AuthenticationSucceededWithUserName": "{0} erfolgreich authentifiziert",
"Books": "Bücher", "Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Kamerabild wurde von {0} hochgeladen",
"Channels": "Kanäle",
"ChapterNameValue": "Kapitel {0}", "ChapterNameValue": "Kapitel {0}",
"Collections": "Sammlungen", "Collections": "Sammlungen",
"DeviceOfflineWithName": "{0} ist offline",
"DeviceOnlineWithName": "{0} ist online",
"FailedLoginAttemptWithUserName": "Anmeldung von {0} fehlgeschlagen", "FailedLoginAttemptWithUserName": "Anmeldung von {0} fehlgeschlagen",
"Favorites": "Favoriten", "Favorites": "Favoriten",
"Folders": "Verzeichnisse", "Folders": "Verzeichnisse",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album-Interpreten",
"HeaderContinueWatching": "Weiterschauen", "HeaderContinueWatching": "Weiterschauen",
"HeaderFavoriteEpisodes": "Lieblingsfolgen", "HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblingsinterpreten",
"HeaderFavoriteEpisodes": "Lieblingsepisoden",
"HeaderFavoriteShows": "Lieblingsserien", "HeaderFavoriteShows": "Lieblingsserien",
"HeaderFavoriteSongs": "Lieblingssongs",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Als Nächstes", "HeaderNextUp": "Als Nächstes",
"HeaderRecordingGroups": "Aufnahme-Gruppen",
"HomeVideos": "Heimvideos", "HomeVideos": "Heimvideos",
"Inherit": "Vererben", "Inherit": "Vererben",
"ItemAddedWithName": "{0} wurde der Bibliothek hinzugefügt",
"ItemRemovedWithName": "{0} wurde aus der Bibliothek entfernt",
"LabelIpAddressValue": "IP-Adresse: {0}", "LabelIpAddressValue": "IP-Adresse: {0}",
"LabelRunningTimeValue": "Laufzeit: {0}", "LabelRunningTimeValue": "Laufzeit: {0}",
"Latest": "Neueste", "Latest": "Neueste",
"MessageApplicationUpdated": "Jellyfin-Server wurde aktualisiert",
"MessageApplicationUpdatedTo": "Jellyfin-Server wurde auf Version {0} aktualisiert",
"MessageNamedServerConfigurationUpdatedWithValue": "Der Server-Einstellungsbereich {0} wurde aktualisiert",
"MessageServerConfigurationUpdated": "Servereinstellungen wurden aktualisiert",
"MixedContent": "Gemischte Inhalte", "MixedContent": "Gemischte Inhalte",
"Movies": "Filme", "Movies": "Filme",
"Music": "Musik", "Music": "Musik",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Video wird abgespielt", "NotificationOptionVideoPlayback": "Video wird abgespielt",
"NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt", "NotificationOptionVideoPlaybackStopped": "Videowiedergabe gestoppt",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Wiedergabelisten",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} wurde installiert", "PluginInstalledWithName": "{0} wurde installiert",
"PluginUninstalledWithName": "{0} wurde deinstalliert", "PluginUninstalledWithName": "{0} wurde deinstalliert",
"PluginUpdatedWithName": "{0} wurde aktualisiert", "PluginUpdatedWithName": "{0} wurde aktualisiert",
"ProviderValue": "Anbieter: {0}",
"ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen", "ScheduledTaskFailedWithName": "{0} ist fehlgeschlagen",
"ScheduledTaskStartedWithName": "{0} wurde gestartet",
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
"Shows": "Serien", "Shows": "Serien",
"Songs": "Lieder",
"StartupEmbyServerIsLoading": "Jellyfin-Server lädt. Bitte versuche es gleich noch einmal.", "StartupEmbyServerIsLoading": "Jellyfin-Server lädt. Bitte versuche es gleich noch einmal.",
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden", "SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
"Sync": "Synchronisation",
"System": "System",
"TvShows": "Serien", "TvShows": "Serien",
"User": "Benutzer",
"UserCreatedWithName": "Benutzer {0} wurde erstellt", "UserCreatedWithName": "Benutzer {0} wurde erstellt",
"UserDeletedWithName": "Benutzer {0} wurde gelöscht", "UserDeletedWithName": "Benutzer {0} wurde gelöscht",
"UserDownloadingItemWithValues": "{0} lädt {1} herunter", "UserDownloadingItemWithValues": "{0} lädt {1} herunter",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} wurde getrennt von {1}", "UserOfflineFromDevice": "{0} wurde getrennt von {1}",
"UserOnlineFromDevice": "{0} ist online von {1}", "UserOnlineFromDevice": "{0} ist online von {1}",
"UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert", "UserPasswordChangedWithName": "Das Passwort für Benutzer {0} wurde geändert",
"UserPolicyUpdatedWithName": "Benutzerrichtlinie von {0} wurde aktualisiert",
"UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet", "UserStartedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} gestartet",
"UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet", "UserStoppedPlayingItemWithValues": "{0} hat die Wiedergabe von {1} auf {2} beendet",
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Extra {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.", "TaskDownloadMissingSubtitlesDescription": "Sucht im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
"TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen", "TaskDownloadMissingSubtitles": "Fehlende Untertitel herunterladen",
@@ -97,6 +126,8 @@
"HearingImpaired": "Hörgeschädigt", "HearingImpaired": "Hörgeschädigt",
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren", "TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",
"TaskRefreshTrickplayImagesDescription": "Erstellt ein Trickplay-Vorschauen 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öscht nicht mehr vorhandene Einträge aus den Sammlungen und Playlisten.",
"TaskAudioNormalization": "Audio Normalisierung", "TaskAudioNormalization": "Audio Normalisierung",
"TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.", "TaskAudioNormalizationDescription": "Durchsucht Dateien nach Audionormalisierungsdaten.",
"TaskDownloadMissingLyricsDescription": "Lädt Songtexte herunter", "TaskDownloadMissingLyricsDescription": "Lädt Songtexte herunter",
@@ -106,7 +137,5 @@
"TaskMoveTrickplayImages": "Verzeichnis für Trickplay-Bilder migrieren", "TaskMoveTrickplayImages": "Verzeichnis für Trickplay-Bilder migrieren",
"TaskMoveTrickplayImagesDescription": "Trickplay-Bilder werden entsprechend der Bibliothekseinstellungen verschoben.", "TaskMoveTrickplayImagesDescription": "Trickplay-Bilder werden entsprechend der Bibliothekseinstellungen verschoben.",
"CleanupUserDataTask": "Aufgabe zur Bereinigung von Benutzerdaten", "CleanupUserDataTask": "Aufgabe zur Bereinigung von Benutzerdaten",
"CleanupUserDataTaskDescription": "Löscht alle Benutzerdaten (Abspielstatus, Favoritenstatus, usw.) von Medien, die seit mindestens 90 Tagen nicht mehr vorhanden sind.", "CleanupUserDataTaskDescription": "Löscht alle Benutzerdaten (Abspielstatus, Favoritenstatus, usw.) von Medien, die seit mindestens 90 Tagen nicht mehr vorhanden sind."
"Original": "Original",
"LyricDownloadFailureFromForItem": "Fehler beim Download der Songtexte von {0} für {1}"
} }

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Άλμπουμ",
"AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}", "AppDeviceValues": "Εφαρμογή: {0}, Συσκευή: {1}",
"Application": "Εφαρμογή",
"Artists": "Καλλιτέχνες", "Artists": "Καλλιτέχνες",
"AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς", "AuthenticationSucceededWithUserName": "Ο χρήστης {0} επαληθεύτηκε επιτυχώς",
"Books": "Βιβλία", "Books": "Βιβλία",
"CameraImageUploadedFrom": "Μια νέα φωτογραφία φορτώθηκε από {0}",
"Channels": "Κανάλια",
"ChapterNameValue": "Κεφάλαιο {0}", "ChapterNameValue": "Κεφάλαιο {0}",
"Collections": "Συλλογές", "Collections": "Συλλογές",
"DeviceOfflineWithName": "Ο/Η {0} αποσυνδέθηκε",
"DeviceOnlineWithName": "Ο/Η {0} συνδέθηκε",
"FailedLoginAttemptWithUserName": "Αποτυχία προσπάθειας σύνδεσης από {0}", "FailedLoginAttemptWithUserName": "Αποτυχία προσπάθειας σύνδεσης από {0}",
"Favorites": "Αγαπημένα", "Favorites": "Αγαπημένα",
"Folders": "Φάκελοι", "Folders": "Φάκελοι",
"Genres": "Είδη", "Genres": "Είδη",
"HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
"HeaderFavoriteEpisodes": "Αγαπημένα Επεισόδια", "HeaderFavoriteEpisodes": "Αγαπημένα Επεισόδια",
"HeaderFavoriteShows": "Αγαπημένες Σειρές", "HeaderFavoriteShows": "Αγαπημένες Σειρές",
"HeaderFavoriteSongs": "Αγαπημένα Τραγούδια",
"HeaderLiveTV": "Ζωντανή Τηλεόραση", "HeaderLiveTV": "Ζωντανή Τηλεόραση",
"HeaderNextUp": "Επόμενο", "HeaderNextUp": "Επόμενο",
"HeaderRecordingGroups": "Ομάδες Ηχογράφησης",
"HomeVideos": "Προσωπικά Βίντεο", "HomeVideos": "Προσωπικά Βίντεο",
"Inherit": "Κληρονόμηση", "Inherit": "Κληρονόμηση",
"ItemAddedWithName": "Το {0} προστέθηκε στη βιβλιοθήκη",
"ItemRemovedWithName": "Το {0} διαγράφτηκε από τη βιβλιοθήκη",
"LabelIpAddressValue": "Διεύθυνση IP: {0}", "LabelIpAddressValue": "Διεύθυνση IP: {0}",
"LabelRunningTimeValue": "Διάρκεια: {0}", "LabelRunningTimeValue": "Διάρκεια: {0}",
"Latest": "Πρόσφατα", "Latest": "Πρόσφατα",
"MessageApplicationUpdated": "Ο διακομιστής Jellyfin έχει ενημερωθεί",
"MessageApplicationUpdatedTo": "Ο διακομιστής Jellyfin αναβαθμίστηκε στην έκδοση {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Η ενότητα {0} ρύθμισης παραμέτρων του διακομιστή έχει ενημερωθεί",
"MessageServerConfigurationUpdated": "Η ρύθμιση παραμέτρων του διακομιστή έχει ενημερωθεί",
"MixedContent": "Ανάμεικτο Περιεχόμενο", "MixedContent": "Ανάμεικτο Περιεχόμενο",
"Movies": "Ταινίες", "Movies": "Ταινίες",
"Music": "Μουσική", "Music": "Μουσική",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε", "NotificationOptionVideoPlayback": "Η αναπαραγωγή βίντεο ξεκίνησε",
"NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε", "NotificationOptionVideoPlaybackStopped": "Η αναπαραγωγή βίντεο σταμάτησε",
"Photos": "Φωτογραφίες", "Photos": "Φωτογραφίες",
"Playlists": "Λίστες αναπαραγωγής",
"Plugin": "Πρόσθετο",
"PluginInstalledWithName": "Το {0} εγκαταστάθηκε", "PluginInstalledWithName": "Το {0} εγκαταστάθηκε",
"PluginUninstalledWithName": "Το {0} έχει απεγκατασταθεί", "PluginUninstalledWithName": "Το {0} έχει απεγκατασταθεί",
"PluginUpdatedWithName": "Το {0} ενημερώθηκε", "PluginUpdatedWithName": "Το {0} ενημερώθηκε",
"ProviderValue": "Πάροχος: {0}",
"ScheduledTaskFailedWithName": "{0} αποτυχία", "ScheduledTaskFailedWithName": "{0} αποτυχία",
"ScheduledTaskStartedWithName": "{0} ξεκίνησε",
"ServerNameNeedsToBeRestarted": "{0} χρειάζεται επανεκκίνηση",
"Shows": "Σειρές", "Shows": "Σειρές",
"Songs": "Τραγούδια",
"StartupEmbyServerIsLoading": "Ο διακομιστής Jellyfin φορτώνει. Περιμένετε λίγο και δοκιμάστε ξανά.", "StartupEmbyServerIsLoading": "Ο διακομιστής Jellyfin φορτώνει. Περιμένετε λίγο και δοκιμάστε ξανά.",
"SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}", "SubtitleDownloadFailureFromForItem": "Αποτυχίες μεταφόρτωσης υποτίτλων από {0} για {1}",
"Sync": "Συγχρονισμός",
"System": "Σύστημα",
"TvShows": "Τηλεοπτικές Σειρές", "TvShows": "Τηλεοπτικές Σειρές",
"User": "Χρήστης",
"UserCreatedWithName": "Ο χρήστης {0} δημιουργήθηκε", "UserCreatedWithName": "Ο χρήστης {0} δημιουργήθηκε",
"UserDeletedWithName": "Ο χρήστης {0} έχει διαγραφεί", "UserDeletedWithName": "Ο χρήστης {0} έχει διαγραφεί",
"UserDownloadingItemWithValues": "{0} κατεβάζει {1}", "UserDownloadingItemWithValues": "{0} κατεβάζει {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} αποσυνδέθηκε από {1}", "UserOfflineFromDevice": "{0} αποσυνδέθηκε από {1}",
"UserOnlineFromDevice": "{0} είναι online απο {1}", "UserOnlineFromDevice": "{0} είναι online απο {1}",
"UserPasswordChangedWithName": "Ο κωδικός του χρήστη {0} έχει αλλάξει", "UserPasswordChangedWithName": "Ο κωδικός του χρήστη {0} έχει αλλάξει",
"UserPolicyUpdatedWithName": "Η πολιτική χρήστη έχει ενημερωθεί για {0}",
"UserStartedPlayingItemWithValues": "{0} παίζει {1} σε {2}", "UserStartedPlayingItemWithValues": "{0} παίζει {1} σε {2}",
"UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}", "UserStoppedPlayingItemWithValues": "{0} τελείωσε να παίζει {1} σε {2}",
"ValueHasBeenAddedToLibrary": "{0} προστέθηκαν στη βιβλιοθήκη πολυμέσων σας",
"ValueSpecialEpisodeName": "Σπέσιαλ - {0}",
"VersionNumber": "Έκδοση {0}", "VersionNumber": "Έκδοση {0}",
"TaskRefreshPeople": "Ανανέωση Ατόμων", "TaskRefreshPeople": "Ανανέωση Ατόμων",
"TaskCleanLogsDescription": "Διαγράφει αρχεία καταγραφής που είναι πάνω από {0} ημέρες.", "TaskCleanLogsDescription": "Διαγράφει αρχεία καταγραφής που είναι πάνω από {0} ημέρες.",
@@ -99,6 +128,8 @@
"TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες.", "TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες.",
"TaskAudioNormalization": "Ομοιομορφία ήχου", "TaskAudioNormalization": "Ομοιομορφία ήχου",
"TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.", "TaskAudioNormalizationDescription": "Ανίχνευση αρχείων για δεδομένα ομοιομορφίας ήχου.",
"TaskCleanCollectionsAndPlaylists": "Καθαρισμός συλλογών και λιστών αναπαραγωγής",
"TaskCleanCollectionsAndPlaylistsDescription": "Αφαιρούνται στοιχεία από τις συλλογές και τις λίστες αναπαραγωγής που δεν υπάρχουν πλέον.",
"TaskMoveTrickplayImages": "Αλλαγή τοποθεσίας εικόνων Trickplay", "TaskMoveTrickplayImages": "Αλλαγή τοποθεσίας εικόνων Trickplay",
"TaskDownloadMissingLyrics": "Λήψη στίχων που λείπουν", "TaskDownloadMissingLyrics": "Λήψη στίχων που λείπουν",
"TaskMoveTrickplayImagesDescription": "Μετακινεί τα υπάρχοντα αρχεία trickplay σύμφωνα με τις ρυθμίσεις της βιβλιοθήκης.", "TaskMoveTrickplayImagesDescription": "Μετακινεί τα υπάρχοντα αρχεία trickplay σύμφωνα με τις ρυθμίσεις της βιβλιοθήκης.",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Device: {1}", "AppDeviceValues": "App: {0}, Device: {1}",
"Application": "Application",
"Artists": "Artists", "Artists": "Artists",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated", "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"Books": "Books", "Books": "Books",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"Channels": "Channels",
"ChapterNameValue": "Chapter {0}", "ChapterNameValue": "Chapter {0}",
"Collections": "Collections", "Collections": "Collections",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favourites", "Favorites": "Favourites",
"Folders": "Folders", "Folders": "Folders",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album artists",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favourite Albums",
"HeaderFavoriteArtists": "Favourite Artists",
"HeaderFavoriteEpisodes": "Favourite Episodes", "HeaderFavoriteEpisodes": "Favourite Episodes",
"HeaderFavoriteShows": "Favourite Shows", "HeaderFavoriteShows": "Favourite Shows",
"HeaderFavoriteSongs": "Favourite Songs",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up", "HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
"HomeVideos": "Home Videos", "HomeVideos": "Home Videos",
"Inherit": "Inherit", "Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
"LabelIpAddressValue": "IP address: {0}", "LabelIpAddressValue": "IP address: {0}",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Running time: {0}",
"Latest": "Latest", "Latest": "Latest",
"MessageApplicationUpdated": "Jellyfin Server has been updated",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MixedContent": "Mixed content", "MixedContent": "Mixed content",
"Movies": "Movies", "Movies": "Movies",
"Music": "Music", "Music": "Music",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped", "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
"Photos": "Photos", "Photos": "Photos",
"Playlists": "Playlists",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed", "PluginInstalledWithName": "{0} was installed",
"PluginUninstalledWithName": "{0} was uninstalled", "PluginUninstalledWithName": "{0} was uninstalled",
"PluginUpdatedWithName": "{0} was updated", "PluginUpdatedWithName": "{0} was updated",
"ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} failed", "ScheduledTaskFailedWithName": "{0} failed",
"ScheduledTaskStartedWithName": "{0} started",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Shows", "Shows": "Shows",
"Songs": "Songs",
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
"Sync": "Sync",
"System": "System",
"TvShows": "TV Shows", "TvShows": "TV Shows",
"User": "User",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "User {0} has been created",
"UserDeletedWithName": "User {0} has been deleted", "UserDeletedWithName": "User {0} has been deleted",
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} is downloading {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} has disconnected from {1}", "UserOfflineFromDevice": "{0} has disconnected from {1}",
"UserOnlineFromDevice": "{0} is online from {1}", "UserOnlineFromDevice": "{0} is online from {1}",
"UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPasswordChangedWithName": "Password has been changed for user {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
"UserStartedPlayingItemWithValues": "{0} has started playing {1}", "UserStartedPlayingItemWithValues": "{0} has started playing {1}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.", "TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
"TaskDownloadMissingSubtitles": "Download missing subtitles", "TaskDownloadMissingSubtitles": "Download missing subtitles",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Audio Normalisation",
"TaskAudioNormalizationDescription": "Scans files for audio normalisation data.", "TaskAudioNormalizationDescription": "Scans files for audio normalisation data.",
"TaskDownloadMissingLyrics": "Download missing lyrics", "TaskDownloadMissingLyrics": "Download missing lyrics",

View File

@@ -1,29 +1,45 @@
{ {
"Albums": "Albums",
"AppDeviceValues": "App: {0}, Device: {1}", "AppDeviceValues": "App: {0}, Device: {1}",
"Application": "Application",
"Artists": "Artists", "Artists": "Artists",
"AuthenticationSucceededWithUserName": "{0} successfully authenticated", "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
"Books": "Books", "Books": "Books",
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
"Channels": "Channels",
"ChapterNameValue": "Chapter {0}", "ChapterNameValue": "Chapter {0}",
"Collections": "Collections", "Collections": "Collections",
"Default": "Default", "Default": "Default",
"DeviceOfflineWithName": "{0} has disconnected",
"DeviceOnlineWithName": "{0} is connected",
"External": "External", "External": "External",
"FailedLoginAttemptWithUserName": "Failed login attempt from {0}", "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
"Favorites": "Favorites", "Favorites": "Favorites",
"Folders": "Folders", "Folders": "Folders",
"Forced": "Forced", "Forced": "Forced",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album artists",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists",
"HeaderFavoriteEpisodes": "Favorite Episodes", "HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteShows": "Favorite Shows", "HeaderFavoriteShows": "Favorite Shows",
"HeaderFavoriteSongs": "Favorite Songs",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up", "HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
"HearingImpaired": "Hearing Impaired", "HearingImpaired": "Hearing Impaired",
"HomeVideos": "Home Videos", "HomeVideos": "Home Videos",
"Inherit": "Inherit", "Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
"LabelIpAddressValue": "IP address: {0}", "LabelIpAddressValue": "IP address: {0}",
"LabelRunningTimeValue": "Running time: {0}", "LabelRunningTimeValue": "Running time: {0}",
"Latest": "Latest", "Latest": "Latest",
"LyricDownloadFailureFromForItem": "Lyrics failed to download from {0} for {1}", "MessageApplicationUpdated": "Jellyfin Server has been updated",
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
"MessageServerConfigurationUpdated": "Server configuration has been updated",
"MixedContent": "Mixed content", "MixedContent": "Mixed content",
"Movies": "Movies", "Movies": "Movies",
"Music": "Music", "Music": "Music",
@@ -48,17 +64,25 @@
"NotificationOptionUserLockedOut": "User locked out", "NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped", "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
"Original": "Original",
"Photos": "Photos", "Photos": "Photos",
"Playlists": "Playlists",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} was installed", "PluginInstalledWithName": "{0} was installed",
"PluginUninstalledWithName": "{0} was uninstalled", "PluginUninstalledWithName": "{0} was uninstalled",
"PluginUpdatedWithName": "{0} was updated", "PluginUpdatedWithName": "{0} was updated",
"ProviderValue": "Provider: {0}",
"ScheduledTaskFailedWithName": "{0} failed", "ScheduledTaskFailedWithName": "{0} failed",
"ScheduledTaskStartedWithName": "{0} started",
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Shows", "Shows": "Shows",
"Songs": "Songs",
"StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.", "StartupEmbyServerIsLoading": "Jellyfin Server is loading. Please try again shortly.",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
"Sync": "Sync",
"System": "System",
"TvShows": "TV Shows", "TvShows": "TV Shows",
"Undefined": "Undefined", "Undefined": "Undefined",
"User": "User",
"UserCreatedWithName": "User {0} has been created", "UserCreatedWithName": "User {0} has been created",
"UserDeletedWithName": "User {0} has been deleted", "UserDeletedWithName": "User {0} has been deleted",
"UserDownloadingItemWithValues": "{0} is downloading {1}", "UserDownloadingItemWithValues": "{0} is downloading {1}",
@@ -66,8 +90,11 @@
"UserOfflineFromDevice": "{0} has disconnected from {1}", "UserOfflineFromDevice": "{0} has disconnected from {1}",
"UserOnlineFromDevice": "{0} is online from {1}", "UserOnlineFromDevice": "{0} is online from {1}",
"UserPasswordChangedWithName": "Password has been changed for user {0}", "UserPasswordChangedWithName": "Password has been changed for user {0}",
"UserPolicyUpdatedWithName": "User policy has been updated for {0}",
"UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}", "UserStartedPlayingItemWithValues": "{0} is playing {1} on {2}",
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}", "UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TasksMaintenanceCategory": "Maintenance", "TasksMaintenanceCategory": "Maintenance",
"TasksLibraryCategory": "Library", "TasksLibraryCategory": "Library",
@@ -103,6 +130,8 @@
"TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.", "TaskOptimizeDatabaseDescription": "Compacts database and truncates free space. Running this task after scanning the library or doing other changes that imply database modifications might improve performance.",
"TaskKeyframeExtractor": "Keyframe Extractor", "TaskKeyframeExtractor": "Keyframe Extractor",
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.", "TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
"TaskCleanCollectionsAndPlaylists": "Clean up collections and playlists",
"TaskCleanCollectionsAndPlaylistsDescription": "Removes items from collections and playlists that no longer exist.",
"TaskExtractMediaSegments": "Media Segment Scan", "TaskExtractMediaSegments": "Media Segment Scan",
"TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.", "TaskExtractMediaSegmentsDescription": "Extracts or obtains media segments from MediaSegment enabled plugins.",
"TaskMoveTrickplayImages": "Migrate Trickplay Image Location", "TaskMoveTrickplayImages": "Migrate Trickplay Image Location",

View File

@@ -7,22 +7,35 @@
"NameInstallFailed": "{0} instalado fiaskis", "NameInstallFailed": "{0} instalado fiaskis",
"Music": "Muziko", "Music": "Muziko",
"Movies": "Filmoj", "Movies": "Filmoj",
"ItemRemovedWithName": "{0} forigis el la plurmediteko",
"ItemAddedWithName": "{0} aldonis al la plurmediteko",
"HeaderLiveTV": "TV-etero", "HeaderLiveTV": "TV-etero",
"HeaderContinueWatching": "Daŭrigi Spektadon", "HeaderContinueWatching": "Daŭrigi Spektadon",
"HeaderAlbumArtists": "Artistoj de albumo",
"Folders": "Dosierujoj", "Folders": "Dosierujoj",
"DeviceOnlineWithName": "{0} estas konektita",
"Default": "Defaŭlte", "Default": "Defaŭlte",
"Collections": "Kolektoj", "Collections": "Kolektoj",
"ChapterNameValue": "Ĉapitro {0}", "ChapterNameValue": "Ĉapitro {0}",
"Channels": "Kanaloj",
"Books": "Libroj", "Books": "Libroj",
"Artists": "Artistoj", "Artists": "Artistoj",
"Application": "Aplikaĵo",
"AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}", "AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
"Albums": "Albumoj",
"TasksLibraryCategory": "Plurmediteko", "TasksLibraryCategory": "Plurmediteko",
"VersionNumber": "Versio {0}", "VersionNumber": "Versio {0}",
"UserDownloadingItemWithValues": "{0} elŝutas {1}", "UserDownloadingItemWithValues": "{0} elŝutas {1}",
"UserCreatedWithName": "Uzanto {0} kreiĝis", "UserCreatedWithName": "Uzanto {0} kreiĝis",
"User": "Uzanto",
"System": "Sistemo",
"Songs": "Kantoj",
"ScheduledTaskStartedWithName": "{0} lanĉis",
"ScheduledTaskFailedWithName": "{0} malsukcesis", "ScheduledTaskFailedWithName": "{0} malsukcesis",
"PluginUninstalledWithName": "{0} malinstaliĝis", "PluginUninstalledWithName": "{0} malinstaliĝis",
"PluginInstalledWithName": "{0} instaliĝis", "PluginInstalledWithName": "{0} instaliĝis",
"Plugin": "Kromprogramo",
"Playlists": "Ludlistoj",
"Photos": "Fotoj", "Photos": "Fotoj",
"NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis", "NotificationOptionPluginUninstalled": "Kromprogramo malinstaliĝis",
"NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis", "NotificationOptionNewLibraryContent": "Nova enhavo aldoniĝis",
@@ -30,28 +43,36 @@
"MusicVideos": "Muzikvideoj", "MusicVideos": "Muzikvideoj",
"LabelIpAddressValue": "IP-adreso: {0}", "LabelIpAddressValue": "IP-adreso: {0}",
"Genres": "Ĝenroj", "Genres": "Ĝenroj",
"DeviceOfflineWithName": "{0} malkonektis",
"HeaderFavoriteArtists": "Favorataj Artistoj",
"Shows": "Serioj", "Shows": "Serioj",
"HeaderFavoriteShows": "Favorataj Serioj", "HeaderFavoriteShows": "Favorataj Serioj",
"TvShows": "TV-serioj", "TvShows": "TV-serioj",
"Favorites": "Favorataj", "Favorites": "Favorataj",
"TaskCleanLogs": "Purigi Ĵurnalan Katalogon", "TaskCleanLogs": "Purigi Ĵurnalan Katalogon",
"TaskRefreshLibrary": "Skani Plurmeditekon", "TaskRefreshLibrary": "Skani Plurmeditekon",
"ValueSpecialEpisodeName": "Speciala - {0}",
"TaskOptimizeDatabase": "Optimumigi datenbazon", "TaskOptimizeDatabase": "Optimumigi datenbazon",
"TaskRefreshChannels": "Refreŝigi Kanalojn", "TaskRefreshChannels": "Refreŝigi Kanalojn",
"TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn", "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn",
"TaskRefreshPeople": "Refreŝigi Homojn", "TaskRefreshPeople": "Refreŝigi Homojn",
"TasksChannelsCategory": "Interretaj Kanaloj", "TasksChannelsCategory": "Interretaj Kanaloj",
"ProviderValue": "Provizanto: {0}",
"NotificationOptionPluginError": "Kromprogramo malsukcesis", "NotificationOptionPluginError": "Kromprogramo malsukcesis",
"MixedContent": "Miksita enhavo", "MixedContent": "Miksita enhavo",
"TasksApplicationCategory": "Aplikaĵo", "TasksApplicationCategory": "Aplikaĵo",
"TasksMaintenanceCategory": "Prizorgado", "TasksMaintenanceCategory": "Prizorgado",
"Undefined": "Nedifinita", "Undefined": "Nedifinita",
"Sync": "Sinkronigo",
"Latest": "Plej novaj", "Latest": "Plej novaj",
"Inherit": "Hereda", "Inherit": "Hereda",
"HomeVideos": "Hejmaj Videoj", "HomeVideos": "Hejmaj Videoj",
"HeaderNextUp": "Sekva Plue", "HeaderNextUp": "Sekva Plue",
"HeaderFavoriteSongs": "Favorataj Kantoj",
"HeaderFavoriteEpisodes": "Favorataj Epizodoj", "HeaderFavoriteEpisodes": "Favorataj Epizodoj",
"HeaderFavoriteAlbums": "Favorataj Albumoj",
"Forced": "Forcita", "Forced": "Forcita",
"ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita",
"NotificationOptionVideoPlayback": "La videoludado lanĉis", "NotificationOptionVideoPlayback": "La videoludado lanĉis",
"NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata", "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata",
"TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la plurmediteka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.", "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la plurmediteka skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.",
@@ -64,16 +85,22 @@
"TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.", "TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.",
"TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.", "TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.",
"TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.", "TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.",
"ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmediteko",
"SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}", "SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}",
"StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.", "StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.",
"TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.", "TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.",
"UserStoppedPlayingItemWithValues": "{0} finis ludi {1} ĉe {2}", "UserStoppedPlayingItemWithValues": "{0} finis ludi {1} ĉe {2}",
"UserPolicyUpdatedWithName": "Uzanta politiko estis ĝisdatigita por {0}",
"UserPasswordChangedWithName": "Pasvorto estis ŝanĝita por uzanto {0}", "UserPasswordChangedWithName": "Pasvorto estis ŝanĝita por uzanto {0}",
"UserStartedPlayingItemWithValues": "{0} ludas {1} ĉe {2}", "UserStartedPlayingItemWithValues": "{0} ludas {1} ĉe {2}",
"UserLockedOutWithName": "Uzanto {0} estas elŝlosita", "UserLockedOutWithName": "Uzanto {0} estas elŝlosita",
"UserOnlineFromDevice": "{0} estas enreta de {1}", "UserOnlineFromDevice": "{0} estas enreta de {1}",
"UserOfflineFromDevice": "{0} malkonektis de {1}", "UserOfflineFromDevice": "{0} malkonektis de {1}",
"UserDeletedWithName": "Uzanto {0} estis forigita", "UserDeletedWithName": "Uzanto {0} estis forigita",
"MessageServerConfigurationUpdated": "Servila agordaro estis ĝisdatigita",
"MessageNamedServerConfigurationUpdatedWithValue": "Servila agorda sekcio {0} estis ĝisdatigita",
"MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}",
"MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita",
"TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.", "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
"TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn", "TaskDownloadMissingSubtitles": "Elŝuti mankantajn subtekstojn",
"TaskCleanTranscode": "Malplenigi Transkodadan Katalogon", "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
@@ -89,7 +116,9 @@
"NotificationOptionApplicationUpdateInstalled": "Aplikaĵa ĝisdatigo instalita", "NotificationOptionApplicationUpdateInstalled": "Aplikaĵa ĝisdatigo instalita",
"NotificationOptionApplicationUpdateAvailable": "Ĝisdatigo de aplikaĵo havebla", "NotificationOptionApplicationUpdateAvailable": "Ĝisdatigo de aplikaĵo havebla",
"LabelRunningTimeValue": "Ludada tempo: {0}", "LabelRunningTimeValue": "Ludada tempo: {0}",
"HeaderRecordingGroups": "Rikordadaj Grupoj",
"FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}", "FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}",
"CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}",
"AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis", "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis",
"TaskKeyframeExtractorDescription": "Eltiras ĉefkadrojn el videodosieroj por krei pli precizajn HLS-ludlistojn. Ĉi tiu tasko povas funkcii dum longa tempo.", "TaskKeyframeExtractorDescription": "Eltiras ĉefkadrojn el videodosieroj por krei pli precizajn HLS-ludlistojn. Ĉi tiu tasko povas funkcii dum longa tempo.",
"TaskKeyframeExtractor": "Eltiri Ĉefkadrojn", "TaskKeyframeExtractor": "Eltiri Ĉefkadrojn",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Álbumes",
"AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
"Application": "Aplicación",
"Artists": "Artistas", "Artists": "Artistas",
"AuthenticationSucceededWithUserName": "{0} autenticado correctamente", "AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
"Books": "Libros", "Books": "Libros",
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
"Channels": "Canales",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado",
"FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión de {0}", "FailedLoginAttemptWithUserName": "Error al intentar iniciar sesión de {0}",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del álbum",
"HeaderContinueWatching": "Seguir viendo", "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Capítulos favoritos", "HeaderFavoriteEpisodes": "Capítulos favoritos",
"HeaderFavoriteShows": "Series favoritas", "HeaderFavoriteShows": "Series favoritas",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "Siguiente", "HeaderNextUp": "Siguiente",
"HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} se ha añadido a la biblioteca",
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}", "LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
"Latest": "Últimos", "Latest": "Últimos",
"MessageApplicationUpdated": "El servidor Jellyfin fue actualizado",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"MixedContent": "Contenido mezclado", "MixedContent": "Contenido mezclado",
"Movies": "Películas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Se inició la reproducción de video", "NotificationOptionVideoPlayback": "Se inició la reproducción de video",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Listas de reproducción",
"Plugin": "Complemento",
"PluginInstalledWithName": "{0} fue instalado", "PluginInstalledWithName": "{0} fue instalado",
"PluginUninstalledWithName": "{0} fue desinstalado", "PluginUninstalledWithName": "{0} fue desinstalado",
"PluginUpdatedWithName": "{0} fue actualizado", "PluginUpdatedWithName": "{0} fue actualizado",
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series", "Shows": "Series",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.", "StartupEmbyServerIsLoading": "El servidor Jellyfin se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}", "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtitulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Series de TV", "TvShows": "Series de TV",
"User": "Usuario",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"UserDeletedWithName": "El usuario {0} ha sido borrado", "UserDeletedWithName": "El usuario {0} ha sido borrado",
"UserDownloadingItemWithValues": "{0} está descargando {1}", "UserDownloadingItemWithValues": "{0} está descargando {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} se ha desconectado de {1}", "UserOfflineFromDevice": "{0} se ha desconectado de {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"UserPolicyUpdatedWithName": "Las política de usuario ha sido actualizada para {0}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.", "TaskDownloadMissingSubtitlesDescription": "Busca en internet los subtítulos que falten basándose en la configuración de los metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
@@ -99,6 +128,8 @@
"TaskRefreshTrickplayImagesDescription": "Crea vistas previas de reproducción engañosa para videos en bibliotecas habilitadas.", "TaskRefreshTrickplayImagesDescription": "Crea vistas previas de reproducción engañosa para videos en bibliotecas habilitadas.",
"TaskAudioNormalization": "Normalización de audio", "TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanea archivos en busca de datos de normalización de audio.", "TaskAudioNormalizationDescription": "Escanea archivos en busca de datos de normalización de audio.",
"TaskCleanCollectionsAndPlaylists": "Limpiar colecciones y listas de reproducción",
"TaskCleanCollectionsAndPlaylistsDescription": "Elimina elementos de colecciones y listas de reproducción que ya no existen.",
"TaskDownloadMissingLyrics": "Descargar letra faltante", "TaskDownloadMissingLyrics": "Descargar letra faltante",
"TaskDownloadMissingLyricsDescription": "Descarga letras de canciones", "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones",
"TaskExtractMediaSegments": "Escanear Segmentos de Media", "TaskExtractMediaSegments": "Escanear Segmentos de Media",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Álbumes",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "App: {0}, Dispositivo: {1}",
"Application": "Aplicación",
"Artists": "Artistas", "Artists": "Artistas",
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito", "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
"Books": "Libros", "Books": "Libros",
"CameraImageUploadedFrom": "Una nueva imagen de cámara ha sido subida desde {0}",
"Channels": "Canales",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {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",
"HeaderAlbumArtists": "Artistas del Álbum",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteShows": "Programas favoritos",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
"HeaderNextUp": "A continuación", "HeaderNextUp": "A continuación",
"HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Videos Caseros", "HomeVideos": "Videos Caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Tiempo corriendo: {0}", "LabelRunningTimeValue": "Tiempo corriendo: {0}",
"Latest": "Recientes", "Latest": "Recientes",
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"MixedContent": "Contenido mezclado", "MixedContent": "Contenido mezclado",
"Movies": "Películas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Reproducción de video iniciada", "NotificationOptionVideoPlayback": "Reproducción de video iniciada",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Listas de reproducción",
"Plugin": "Complemento",
"PluginInstalledWithName": "{0} fue instalado", "PluginInstalledWithName": "{0} fue instalado",
"PluginUninstalledWithName": "{0} fue desinstalado", "PluginUninstalledWithName": "{0} fue desinstalado",
"PluginUpdatedWithName": "{0} fue actualizado", "PluginUpdatedWithName": "{0} fue actualizado",
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciado",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Programas", "Shows": "Programas",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Programas de TV", "TvShows": "Programas de TV",
"User": "Usuario",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"UserDeletedWithName": "El usuario {0} ha sido eliminado", "UserDeletedWithName": "El usuario {0} ha sido eliminado",
"UserDownloadingItemWithValues": "{0} está descargando {1}", "UserDownloadingItemWithValues": "{0} está descargando {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} se ha desconectado desde {1}", "UserOfflineFromDevice": "{0} se ha desconectado desde {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios",
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
@@ -99,6 +128,8 @@
"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", "TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el 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.",
"TaskDownloadMissingLyrics": "descargar letras que faltan", "TaskDownloadMissingLyrics": "descargar letras que faltan",
"TaskDownloadMissingLyricsDescription": "Descargar letras de canciones", "TaskDownloadMissingLyricsDescription": "Descargar letras de canciones",
"TaskExtractMediaSegments": "Escaneo de segmentos de medios", "TaskExtractMediaSegments": "Escaneo de segmentos de medios",

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Álbumes",
"AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}", "AppDeviceValues": "Aplicación: {0}, Dispositivo: {1}",
"Application": "Aplicación",
"Artists": "Artistas", "Artists": "Artistas",
"AuthenticationSucceededWithUserName": "{0} autenticado correctamente", "AuthenticationSucceededWithUserName": "{0} autenticado correctamente",
"Books": "Libros", "Books": "Libros",
"CameraImageUploadedFrom": "Se ha subido una nueva imagen por cámara desde {0}",
"Channels": "Canales",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
"Collections": "Colecciones", "Collections": "Colecciones",
"DeviceOfflineWithName": "{0} se ha desconectado",
"DeviceOnlineWithName": "{0} está conectado",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión de {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",
"HeaderAlbumArtists": "Artistas del álbum",
"HeaderContinueWatching": "Seguir viendo", "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Series favoritas", "HeaderFavoriteShows": "Series favoritas",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "Televisión en directo", "HeaderLiveTV": "Televisión en directo",
"HeaderNextUp": "Siguiente", "HeaderNextUp": "Siguiente",
"HeaderRecordingGroups": "Grupos de grabación",
"HomeVideos": "Vídeos caseros", "HomeVideos": "Vídeos caseros",
"Inherit": "Heredar", "Inherit": "Heredar",
"ItemAddedWithName": "{0} se ha añadido a la biblioteca",
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"LabelRunningTimeValue": "Duración: {0}", "LabelRunningTimeValue": "Duración: {0}",
"Latest": "Últimas", "Latest": "Últimas",
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"MixedContent": "Contenido mixto", "MixedContent": "Contenido mixto",
"Movies": "Películas", "Movies": "Películas",
"Music": "Música", "Music": "Música",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo", "NotificationOptionVideoPlayback": "Se inició la reproducción de vídeo",
"NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de vídeo detenida",
"Photos": "Fotos", "Photos": "Fotos",
"Playlists": "Listas de reproducción",
"Plugin": "Plugin",
"PluginInstalledWithName": "{0} se ha instalado", "PluginInstalledWithName": "{0} se ha instalado",
"PluginUninstalledWithName": "{0} se ha desinstalado", "PluginUninstalledWithName": "{0} se ha desinstalado",
"PluginUpdatedWithName": "{0} se actualizó", "PluginUpdatedWithName": "{0} se actualizó",
"ProviderValue": "Proveedor: {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ScheduledTaskStartedWithName": "{0} iniciada",
"ServerNameNeedsToBeRestarted": "{0} necesita ser reiniciado",
"Shows": "Series", "Shows": "Series",
"Songs": "Canciones",
"StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.", "StartupEmbyServerIsLoading": "Jellyfin Server se está cargando. Vuelve a intentarlo en breve.",
"SubtitleDownloadFailureFromForItem": "Fallo en la descarga de subtítulos desde {0} para {1}", "SubtitleDownloadFailureFromForItem": "Fallo en la descarga de subtítulos desde {0} para {1}",
"Sync": "Sincronizar",
"System": "Sistema",
"TvShows": "Series", "TvShows": "Series",
"User": "Usuario",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"UserDeletedWithName": "El usuario {0} ha sido borrado", "UserDeletedWithName": "El usuario {0} ha sido borrado",
"UserDownloadingItemWithValues": "{0} está descargando {1}", "UserDownloadingItemWithValues": "{0} está descargando {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} se ha desconectado desde {1}", "UserOfflineFromDevice": "{0} se ha desconectado desde {1}",
"UserOnlineFromDevice": "{0} está en línea desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"UserPolicyUpdatedWithName": "Actualizada política de usuario para {0}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"ValueHasBeenAddedToLibrary": "{0} ha sido añadido a tu biblioteca multimedia",
"ValueSpecialEpisodeName": "Especial - {0}",
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"TasksMaintenanceCategory": "Mantenimiento", "TasksMaintenanceCategory": "Mantenimiento",
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización.", "TaskAudioNormalizationDescription": "Escanear archivos para obtener datos de normalización.",
"TaskDownloadMissingLyricsDescription": "Descargar letras para las canciones", "TaskDownloadMissingLyricsDescription": "Descargar letras para las canciones",
@@ -106,6 +137,5 @@
"TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.", "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de plugins habilitados para MediaSegment.",
"TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay", "TaskMoveTrickplayImages": "Migrar la ubicación de la imagen de Trickplay",
"CleanupUserDataTask": "Tarea de limpieza de datos del usuario", "CleanupUserDataTask": "Tarea de limpieza de datos del usuario",
"CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, favoritos, etc.) de los medios que ya no están disponibles desde hace al menos 90 días.", "CleanupUserDataTaskDescription": "Limpia todos los datos del usuario (estado de visualización, favoritos, etc.) de los medios que ya no están disponibles desde hace al menos 90 días."
"Original": "Original"
} }

View File

@@ -1,19 +1,29 @@
{ {
"LabelRunningTimeValue": "Tiempo en ejecución: {0}", "LabelRunningTimeValue": "Tiempo en ejecución: {0}",
"ValueSpecialEpisodeName": "Especial - {0}",
"Sync": "Sincronizar",
"Songs": "Canciones",
"Shows": "Programas", "Shows": "Programas",
"Playlists": "Listas de reproducción",
"Photos": "Fotos", "Photos": "Fotos",
"Movies": "Películas", "Movies": "Películas",
"HeaderNextUp": "A continuación", "HeaderNextUp": "A continuación",
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos", "HeaderFavoriteEpisodes": "Episodios favoritos",
"HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteShows": "Programas favoritos",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderAlbumArtists": "Artistas de álbum",
"Genres": "Géneros", "Genres": "Géneros",
"Folders": "Carpetas", "Folders": "Carpetas",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Collections": "Colecciones", "Collections": "Colecciones",
"Channels": "Canales",
"Books": "Libros", "Books": "Libros",
"Artists": "Artistas", "Artists": "Artistas",
"Albums": "Álbumes",
"TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.", "TaskDownloadMissingSubtitlesDescription": "Busca subtítulos faltantes en Internet basándose en la configuración de metadatos.",
"TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes", "TaskDownloadMissingSubtitles": "Descargar subtítulos faltantes",
"TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.", "TaskRefreshChannelsDescription": "Actualiza la información de canales de Internet.",
@@ -37,8 +47,10 @@
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Mantenimiento", "TasksMaintenanceCategory": "Mantenimiento",
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca de medios",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"UserOnlineFromDevice": "{0} está en línea desde {1}", "UserOnlineFromDevice": "{0} está en línea desde {1}",
"UserOfflineFromDevice": "{0} se ha desconectado desde {1}", "UserOfflineFromDevice": "{0} se ha desconectado desde {1}",
@@ -46,13 +58,19 @@
"UserDownloadingItemWithValues": "{0} está descargando {1}", "UserDownloadingItemWithValues": "{0} está descargando {1}",
"UserDeletedWithName": "El usuario {0} ha sido eliminado", "UserDeletedWithName": "El usuario {0} ha sido eliminado",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"User": "Usuario",
"TvShows": "Programas de TV", "TvShows": "Programas de TV",
"System": "Sistema",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}",
"StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.", "StartupEmbyServerIsLoading": "El servidor Jellyfin está cargando. Por favor, intente de nuevo pronto.",
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
"ScheduledTaskStartedWithName": "{0} iniciado",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"ProviderValue": "Proveedor: {0}",
"PluginUpdatedWithName": "{0} fue actualizado", "PluginUpdatedWithName": "{0} fue actualizado",
"PluginUninstalledWithName": "{0} fue desinstalado", "PluginUninstalledWithName": "{0} fue desinstalado",
"PluginInstalledWithName": "{0} fue instalado", "PluginInstalledWithName": "{0} fue instalado",
"Plugin": "Complemento",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"NotificationOptionVideoPlayback": "Reproducción de video iniciada", "NotificationOptionVideoPlayback": "Reproducción de video iniciada",
"NotificationOptionUserLockedOut": "Usuario bloqueado", "NotificationOptionUserLockedOut": "Usuario bloqueado",
@@ -76,13 +94,24 @@
"MusicVideos": "Videos musicales", "MusicVideos": "Videos musicales",
"Music": "Música", "Music": "Música",
"MixedContent": "Contenido mezclado", "MixedContent": "Contenido mezclado",
"MessageServerConfigurationUpdated": "Se ha actualizado la configuración del servidor",
"MessageNamedServerConfigurationUpdatedWithValue": "Se ha actualizado la sección {0} de la configuración del servidor",
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
"Latest": "Recientes", "Latest": "Recientes",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"Inherit": "Heredar", "Inherit": "Heredar",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos caseros",
"HeaderRecordingGroups": "Grupos de grabación",
"FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}", "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {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",
"AppDeviceValues": "Aplicación: {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",
@@ -98,7 +127,9 @@
"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", "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.", "TaskAudioNormalizationDescription": "Analiza los archivos para normalizar el audio.",
"TaskCleanCollectionsAndPlaylists": "Limpieza de colecciones y listas de reproducción",
"TaskDownloadMissingLyrics": "Descargar letra faltante", "TaskDownloadMissingLyrics": "Descargar letra faltante",
"TaskDownloadMissingLyricsDescription": "Descarga letras de canciones", "TaskDownloadMissingLyricsDescription": "Descarga letras de canciones",
"TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de complementos habilitados para MediaSegment.", "TaskExtractMediaSegmentsDescription": "Extrae u obtiene segmentos de medios de complementos habilitados para MediaSegment.",

View File

@@ -1,24 +1,35 @@
{ {
"Channels": "Canales",
"Books": "Libros", "Books": "Libros",
"Albums": "Álbumes",
"Collections": "Colecciones", "Collections": "Colecciones",
"Artists": "Artistas", "Artists": "Artistas",
"DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado",
"ChapterNameValue": "Capítulo {0}", "ChapterNameValue": "Capítulo {0}",
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito", "AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
"Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "App: {0}, Dispositivo: {1}",
"HeaderContinueWatching": "Continuar Viendo", "HeaderContinueWatching": "Continuar Viendo",
"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 desde {0}", "FailedLoginAttemptWithUserName": "Intento de inicio de sesión fallido desde {0}",
"HeaderFavoriteSongs": "Canciones Favoritas",
"HeaderFavoriteEpisodes": "Episodios Favoritos", "HeaderFavoriteEpisodes": "Episodios Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos",
"External": "Externo", "External": "Externo",
"Default": "Predeterminado", "Default": "Predeterminado",
"Movies": "Películas", "Movies": "Películas",
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de la configuración ha sido actualizada",
"MixedContent": "Contenido mixto", "MixedContent": "Contenido mixto",
"Music": "Música", "Music": "Música",
"NotificationOptionCameraImageUploaded": "Imagen de la cámara subida", "NotificationOptionCameraImageUploaded": "Imagen de la cámara subida",
"NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor", "NotificationOptionServerRestartRequired": "Se necesita reiniciar el servidor",
"NotificationOptionVideoPlayback": "Reproducción de video iniciada", "NotificationOptionVideoPlayback": "Reproducción de video iniciada",
"Sync": "Sincronizar",
"Shows": "Series", "Shows": "Series",
"UserDownloadingItemWithValues": "{0} está descargando {1}", "UserDownloadingItemWithValues": "{0} está descargando {1}",
"UserOfflineFromDevice": "{0} se ha desconectado desde {1}", "UserOfflineFromDevice": "{0} se ha desconectado desde {1}",
@@ -30,6 +41,8 @@
"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.", "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", "TaskAudioNormalization": "Normalización de audio",
"TaskAudioNormalizationDescription": "Escanear archivos para la normalización de data.", "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", "TvShows": "Series de TV",
"UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}", "UserStartedPlayingItemWithValues": "{0} está reproduciendo {1} en {2}",
"TaskRefreshChannels": "Actualizar canales", "TaskRefreshChannels": "Actualizar canales",
@@ -37,12 +50,17 @@
"HeaderFavoriteShows": "Programas favoritos", "HeaderFavoriteShows": "Programas favoritos",
"TaskCleanActivityLog": "Limpiar registro de actividades", "TaskCleanActivityLog": "Limpiar registro de actividades",
"UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}", "UserPasswordChangedWithName": "Se ha cambiado la contraseña para el usuario {0}",
"System": "Sistema",
"User": "Usuario",
"Forced": "Forzado", "Forced": "Forzado",
"PluginInstalledWithName": "{0} ha sido instalado", "PluginInstalledWithName": "{0} ha sido instalado",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"TaskUpdatePlugins": "Actualizar Plugins", "TaskUpdatePlugins": "Actualizar Plugins",
"Latest": "Recientes", "Latest": "Recientes",
"UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}", "UserStoppedPlayingItemWithValues": "{0} ha terminado de reproducir {1} en {2}",
"Songs": "Canciones",
"NotificationOptionPluginError": "Falla de plugin", "NotificationOptionPluginError": "Falla de plugin",
"ScheduledTaskStartedWithName": "{0} iniciado",
"TasksApplicationCategory": "Aplicación", "TasksApplicationCategory": "Aplicación",
"UserDeletedWithName": "El usuario {0} ha sido eliminado", "UserDeletedWithName": "El usuario {0} ha sido eliminado",
"TaskRefreshChapterImages": "Extraer imágenes de los capítulos", "TaskRefreshChapterImages": "Extraer imágenes de los capítulos",
@@ -55,26 +73,34 @@
"NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida", "NotificationOptionAudioPlaybackStopped": "Reproducción de audio detenida",
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
"NotificationOptionPluginInstalled": "Plugin instalado", "NotificationOptionPluginInstalled": "Plugin instalado",
"UserPolicyUpdatedWithName": "La política de usuario ha sido actualizada para {0}",
"VersionNumber": "Versión {0}", "VersionNumber": "Versión {0}",
"HeaderNextUp": "A continuación", "HeaderNextUp": "A continuación",
"ValueHasBeenAddedToLibrary": "{0} se ha añadido a tu biblioteca",
"LabelIpAddressValue": "Dirección IP: {0}", "LabelIpAddressValue": "Dirección IP: {0}",
"NameSeasonNumber": "Temporada {0}", "NameSeasonNumber": "Temporada {0}",
"NotificationOptionNewLibraryContent": "Nuevo contenido agregado", "NotificationOptionNewLibraryContent": "Nuevo contenido agregado",
"Plugin": "Plugin",
"NotificationOptionAudioPlayback": "Reproducción de audio iniciada", "NotificationOptionAudioPlayback": "Reproducción de audio iniciada",
"NotificationOptionTaskFailed": "Falló la tarea programada", "NotificationOptionTaskFailed": "Falló la tarea programada",
"LabelRunningTimeValue": "Tiempo en ejecución: {0}", "LabelRunningTimeValue": "Tiempo en ejecución: {0}",
"SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}", "SubtitleDownloadFailureFromForItem": "Falló la descarga de subtítulos desde {0} para {1}",
"TaskRefreshLibrary": "Escanear biblioteca de medios", "TaskRefreshLibrary": "Escanear biblioteca de medios",
"ServerNameNeedsToBeRestarted": "{0} debe ser reiniciado",
"TasksMaintenanceCategory": "Mantenimiento", "TasksMaintenanceCategory": "Mantenimiento",
"ProviderValue": "Proveedor: {0}",
"UserCreatedWithName": "El usuario {0} ha sido creado", "UserCreatedWithName": "El usuario {0} ha sido creado",
"PluginUninstalledWithName": "{0} ha sido desinstalado", "PluginUninstalledWithName": "{0} ha sido desinstalado",
"ValueSpecialEpisodeName": "Especial - {0}",
"ScheduledTaskFailedWithName": "{0} falló", "ScheduledTaskFailedWithName": "{0} falló",
"TaskCleanLogs": "Limpiar directorio de registros", "TaskCleanLogs": "Limpiar directorio de registros",
"NameInstallFailed": "Falló la instalación de {0}", "NameInstallFailed": "Falló la instalación de {0}",
"UserLockedOutWithName": "El usuario {0} ha sido bloqueado", "UserLockedOutWithName": "El usuario {0} ha sido bloqueado",
"TaskRefreshLibraryDescription": "Escanea tu biblioteca de medios para encontrar archivos nuevos y actualizar los metadatos.", "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.", "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.", "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", "TaskRefreshPeople": "Actualizar personas",
"NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida", "NotificationOptionVideoPlaybackStopped": "Reproducción de video detenida",
"HeaderLiveTV": "TV en vivo", "HeaderLiveTV": "TV en vivo",
@@ -84,10 +110,15 @@
"TaskCleanCache": "Limpiar directorio caché", "TaskCleanCache": "Limpiar directorio caché",
"TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.", "TaskRefreshChapterImagesDescription": "Crea miniaturas para videos que tienen capítulos.",
"Inherit": "Heredar", "Inherit": "Heredar",
"HeaderRecordingGroups": "Grupos de grabación",
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
"TaskOptimizeDatabase": "Optimizar base de datos", "TaskOptimizeDatabase": "Optimizar base de datos",
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave", "TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
"HearingImpaired": "Discapacidad auditiva", "HearingImpaired": "Discapacidad auditiva",
"HomeVideos": "Videos caseros", "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", "MusicVideos": "Videos musicales",
"NewVersionIsAvailable": "Una nueva versión de Jellyfin está disponible para descargar.", "NewVersionIsAvailable": "Una nueva versión de Jellyfin está disponible para descargar.",
"PluginUpdatedWithName": "{0} ha sido actualizado", "PluginUpdatedWithName": "{0} ha sido actualizado",

View File

@@ -1,6 +1,7 @@
{ {
"TaskCleanActivityLogDescription": "Kustutab määratud ajast vanemad tegevuslogi kirjed.", "TaskCleanActivityLogDescription": "Kustutab määratud ajast vanemad tegevuslogi kirjed.",
"UserDownloadingItemWithValues": "{0} laadib alla {1}", "UserDownloadingItemWithValues": "{0} laadib alla {1}",
"HeaderRecordingGroups": "Salvestusrühmad",
"TaskOptimizeDatabaseDescription": "Tihendab ja puhastab andmebaasi. Selle toimingu tegemine pärast meediakogu andmebaasiga seotud muudatuste skannimist võib jõudlust parandada.", "TaskOptimizeDatabaseDescription": "Tihendab ja puhastab andmebaasi. Selle toimingu tegemine pärast meediakogu andmebaasiga seotud muudatuste skannimist võib jõudlust parandada.",
"TaskOptimizeDatabase": "Optimeeri andmebaasi", "TaskOptimizeDatabase": "Optimeeri andmebaasi",
"TaskDownloadMissingSubtitlesDescription": "Otsib veebist puuduvaid subtiitreid vastavalt määratud metaandmete seadetele.", "TaskDownloadMissingSubtitlesDescription": "Otsib veebist puuduvaid subtiitreid vastavalt määratud metaandmete seadetele.",
@@ -28,19 +29,30 @@
"TasksLibraryCategory": "Meediakogu", "TasksLibraryCategory": "Meediakogu",
"TasksMaintenanceCategory": "Hooldus", "TasksMaintenanceCategory": "Hooldus",
"VersionNumber": "Versioon {0}", "VersionNumber": "Versioon {0}",
"ValueSpecialEpisodeName": "Eriepisood - {0}",
"ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse",
"UserStartedPlayingItemWithValues": "{0} taasesitab {1} seadmes {2}", "UserStartedPlayingItemWithValues": "{0} taasesitab {1} seadmes {2}",
"UserPasswordChangedWithName": "Kasutaja {0} parool muudeti", "UserPasswordChangedWithName": "Kasutaja {0} parool muudeti",
"UserLockedOutWithName": "Kasutaja {0} lukustati", "UserLockedOutWithName": "Kasutaja {0} lukustati",
"UserDeletedWithName": "Kasutaja {0} kustutati", "UserDeletedWithName": "Kasutaja {0} kustutati",
"UserCreatedWithName": "Kasutaja {0} on loodud", "UserCreatedWithName": "Kasutaja {0} on loodud",
"ScheduledTaskStartedWithName": "{0} käivitati",
"ProviderValue": "Allikas: {0}",
"StartupEmbyServerIsLoading": "Jellyfin server laadib. Proovi varsti uuesti.", "StartupEmbyServerIsLoading": "Jellyfin server laadib. Proovi varsti uuesti.",
"User": "Kasutaja",
"Undefined": "Määratlemata", "Undefined": "Määratlemata",
"TvShows": "Sarjad", "TvShows": "Sarjad",
"System": "Süsteem",
"Sync": "Sünkrooni",
"Songs": "Lood",
"Shows": "Sarjad", "Shows": "Sarjad",
"ServerNameNeedsToBeRestarted": "{0} tuleb taaskäivitada",
"ScheduledTaskFailedWithName": "{0} nurjus", "ScheduledTaskFailedWithName": "{0} nurjus",
"PluginUpdatedWithName": "{0} uuendati", "PluginUpdatedWithName": "{0} uuendati",
"PluginUninstalledWithName": "{0} eemaldati", "PluginUninstalledWithName": "{0} eemaldati",
"PluginInstalledWithName": "{0} paigaldati", "PluginInstalledWithName": "{0} paigaldati",
"Plugin": "Plugin",
"Playlists": "Esitusloendid",
"Photos": "Fotod", "Photos": "Fotod",
"NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes", "NotificationOptionVideoPlaybackStopped": "Video taasesitus lõppes",
"NotificationOptionVideoPlayback": "Video taasesitus algas", "NotificationOptionVideoPlayback": "Video taasesitus algas",
@@ -66,29 +78,46 @@
"Music": "Muusika", "Music": "Muusika",
"Movies": "Filmid", "Movies": "Filmid",
"MixedContent": "Segatud sisu", "MixedContent": "Segatud sisu",
"MessageServerConfigurationUpdated": "Serveri seadistust uuendati",
"MessageNamedServerConfigurationUpdatedWithValue": "Serveri seadistusosa {0} uuendati",
"MessageApplicationUpdatedTo": "Jellyfin server uuendati versioonile {0}",
"MessageApplicationUpdated": "Jellyfin server uuendati",
"Latest": "Uusimad", "Latest": "Uusimad",
"LabelRunningTimeValue": "Kestus: {0}", "LabelRunningTimeValue": "Kestus: {0}",
"LabelIpAddressValue": "IP aadress: {0}", "LabelIpAddressValue": "IP aadress: {0}",
"ItemRemovedWithName": "{0} eemaldati meediakogust",
"ItemAddedWithName": "{0} lisati meediakogusse",
"Inherit": "Päri", "Inherit": "Päri",
"HomeVideos": "Koduvideod", "HomeVideos": "Koduvideod",
"HeaderNextUp": "Järgmisena", "HeaderNextUp": "Järgmisena",
"HeaderLiveTV": "Otse TV", "HeaderLiveTV": "Otse TV",
"HeaderFavoriteSongs": "Lemmiklood",
"HeaderFavoriteShows": "Lemmiksarjad", "HeaderFavoriteShows": "Lemmiksarjad",
"HeaderFavoriteEpisodes": "Lemmikepisoodid", "HeaderFavoriteEpisodes": "Lemmikepisoodid",
"HeaderFavoriteArtists": "Lemmikesitajad",
"HeaderFavoriteAlbums": "Lemmikalbumid",
"HeaderContinueWatching": "Jätka vaatamist", "HeaderContinueWatching": "Jätka vaatamist",
"HeaderAlbumArtists": "Albumi esitajad",
"Genres": "Žanrid", "Genres": "Žanrid",
"Forced": "Sunnitud", "Forced": "Sunnitud",
"Folders": "Kaustad", "Folders": "Kaustad",
"Favorites": "Lemmikud", "Favorites": "Lemmikud",
"FailedLoginAttemptWithUserName": "Sisselogimine nurjus aadressilt {0}", "FailedLoginAttemptWithUserName": "Sisselogimine nurjus aadressilt {0}",
"DeviceOnlineWithName": "{0} on ühendatud",
"DeviceOfflineWithName": "{0} katkestas ühenduse",
"Default": "Vaikimisi", "Default": "Vaikimisi",
"ChapterNameValue": "Peatükk {0}", "ChapterNameValue": "Peatükk {0}",
"Channels": "Kanalid",
"CameraImageUploadedFrom": "Uus kaamera pilt laaditi üles allikalt {0}",
"Books": "Raamatud", "Books": "Raamatud",
"AuthenticationSucceededWithUserName": "{0} autentimine õnnestus", "AuthenticationSucceededWithUserName": "{0} autentimine õnnestus",
"Artists": "Esitajad", "Artists": "Esitajad",
"Application": "Rakendus",
"AppDeviceValues": "Rakendus: {0}, seade: {1}", "AppDeviceValues": "Rakendus: {0}, seade: {1}",
"Albums": "Albumid",
"UserOfflineFromDevice": "{0} katkestas ühenduse seadmega {1}", "UserOfflineFromDevice": "{0} katkestas ühenduse seadmega {1}",
"SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus", "SubtitleDownloadFailureFromForItem": "Subtiitrite allalaadimine {0} > {1} nurjus",
"UserPolicyUpdatedWithName": "Kasutaja {0} õigusi värskendati",
"UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}", "UserStoppedPlayingItemWithValues": "{0} lõpetas {1} taasesituse seadmes {2}",
"UserOnlineFromDevice": "{0} on ühendatud seadmest {1}", "UserOnlineFromDevice": "{0} on ühendatud seadmest {1}",
"External": "Väline", "External": "Väline",
@@ -99,14 +128,14 @@
"TaskRefreshTrickplayImagesDescription": "Loob trickplay eelvaated videotele lubatud meediakogudes.", "TaskRefreshTrickplayImagesDescription": "Loob trickplay eelvaated videotele lubatud meediakogudes.",
"TaskAudioNormalization": "Normaliseeri helitugevus", "TaskAudioNormalization": "Normaliseeri helitugevus",
"TaskAudioNormalizationDescription": "Otsib failidest helitugevuse normaliseerimise teavet.", "TaskAudioNormalizationDescription": "Otsib failidest helitugevuse normaliseerimise teavet.",
"TaskCleanCollectionsAndPlaylistsDescription": "Eemaldab kogumikest ja esitusloenditest üksused, mida enam ei eksisteeri.",
"TaskCleanCollectionsAndPlaylists": "Puhasta kogumikud ja esitusloendid",
"TaskDownloadMissingLyrics": "Hangi puuduvad laulusõnad", "TaskDownloadMissingLyrics": "Hangi puuduvad laulusõnad",
"TaskDownloadMissingLyricsDescription": "Laulusõnade allalaadimine", "TaskDownloadMissingLyricsDescription": "Laulusõnade allalaadimine",
"TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.", "TaskMoveTrickplayImagesDescription": "Liigutab trickplay pildid meediakogu sätete kohaselt.",
"TaskExtractMediaSegments": "Skaneeri meedialõike", "TaskExtractMediaSegments": "Skaneeri meediasegmente",
"TaskExtractMediaSegmentsDescription": "Eraldab või võtab meedialõigud MediaSegment'i toega pluginatest.", "TaskExtractMediaSegmentsDescription": "Eraldab või võtab meediasegmendid MediaSegment'i lubavatest pluginatest.",
"TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht", "TaskMoveTrickplayImages": "Muuda trickplay piltide asukoht",
"CleanupUserDataTask": "Puhasta kasutajaandmed", "CleanupUserDataTask": "Puhasta kasutajaandmed",
"CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud.", "CleanupUserDataTaskDescription": "Puhastab kõik kasutajaandmed (vaatamise olek, lemmikute olek jne) meediast, mida pole enam vähemalt 90 päeva saadaval olnud."
"LyricDownloadFailureFromForItem": "Laulusõnade hankimine teenusest {0} loole {1} nurjus",
"Original": "Algne"
} }

View File

@@ -1,16 +1,23 @@
{ {
"ValueSpecialEpisodeName": "Berezia - {0}",
"Sync": "Sinkronizatu",
"Songs": "Abestiak",
"Shows": "Serieak", "Shows": "Serieak",
"Playlists": "Erreprodukzio-zerrendak",
"Photos": "Argazkiak", "Photos": "Argazkiak",
"MusicVideos": "Bideo musikalak", "MusicVideos": "Bideo musikalak",
"Movies": "Filmak", "Movies": "Filmak",
"HeaderContinueWatching": "Ikusten jarraitu", "HeaderContinueWatching": "Ikusten jarraitu",
"HeaderAlbumArtists": "Albumeko artistak",
"Genres": "Generoak", "Genres": "Generoak",
"Folders": "Karpetak", "Folders": "Karpetak",
"Favorites": "Gogokoak", "Favorites": "Gogokoak",
"Default": "Lehenetsia", "Default": "Lehenetsia",
"Collections": "Bildumak", "Collections": "Bildumak",
"Channels": "Kanalak",
"Books": "Liburuak", "Books": "Liburuak",
"Artists": "Artistak", "Artists": "Artistak",
"Albums": "Albumak",
"TaskOptimizeDatabase": "Datu basea optimizatu", "TaskOptimizeDatabase": "Datu basea optimizatu",
"TaskDownloadMissingSubtitlesDescription": "Falta diren azpitituluak bilatzen ditu interneten metadatuen konfigurazioaren arabera.", "TaskDownloadMissingSubtitlesDescription": "Falta diren azpitituluak bilatzen ditu interneten metadatuen konfigurazioaren arabera.",
"TaskDownloadMissingSubtitles": "Falta diren azpitituluak deskargatu", "TaskDownloadMissingSubtitles": "Falta diren azpitituluak deskargatu",
@@ -37,8 +44,10 @@
"TasksLibraryCategory": "Liburutegia", "TasksLibraryCategory": "Liburutegia",
"TasksMaintenanceCategory": "Mantenua", "TasksMaintenanceCategory": "Mantenua",
"VersionNumber": "Bertsioa {0}", "VersionNumber": "Bertsioa {0}",
"ValueHasBeenAddedToLibrary": "{0} zure multimedia liburutegian gehitu da",
"UserStoppedPlayingItemWithValues": "{0} {1} ikusten bukatu du {2}-(e)n", "UserStoppedPlayingItemWithValues": "{0} {1} ikusten bukatu du {2}-(e)n",
"UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(e)n", "UserStartedPlayingItemWithValues": "{0} {1} ikusten ari da {2}-(e)n",
"UserPolicyUpdatedWithName": "{0} erabiltzailearen politikak aldatu dira",
"UserPasswordChangedWithName": "{0} erabiltzailearen pasahitza aldatu da", "UserPasswordChangedWithName": "{0} erabiltzailearen pasahitza aldatu da",
"UserOnlineFromDevice": "{0} online dago {1}-(e)tik", "UserOnlineFromDevice": "{0} online dago {1}-(e)tik",
"UserOfflineFromDevice": "{0} {1}-(e)tik deskonektatu da", "UserOfflineFromDevice": "{0} {1}-(e)tik deskonektatu da",
@@ -46,14 +55,19 @@
"UserDownloadingItemWithValues": "{0} {1} deskargatzen ari da", "UserDownloadingItemWithValues": "{0} {1} deskargatzen ari da",
"UserDeletedWithName": "{0} Erabiltzailea ezabatu da", "UserDeletedWithName": "{0} Erabiltzailea ezabatu da",
"UserCreatedWithName": "{0} Erabiltzailea sortu da", "UserCreatedWithName": "{0} Erabiltzailea sortu da",
"User": "Erabiltzailea",
"Undefined": "Ezezaguna", "Undefined": "Ezezaguna",
"TvShows": "TB serieak", "TvShows": "TB serieak",
"System": "Sistema",
"SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0}-tik deskargatzeak huts egin du", "SubtitleDownloadFailureFromForItem": "{1}-en azpitutuluak {0}-tik deskargatzeak huts egin du",
"StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduago.", "StartupEmbyServerIsLoading": "Jellyfin zerbitzaria kargatzen. Saiatu berriro beranduago.",
"ServerNameNeedsToBeRestarted": "{0} berrabiarazi behar da",
"ScheduledTaskStartedWithName": "{0} hasi da",
"ScheduledTaskFailedWithName": "{0} huts egin du", "ScheduledTaskFailedWithName": "{0} huts egin du",
"PluginUpdatedWithName": "{0} eguneratu da", "PluginUpdatedWithName": "{0} eguneratu da",
"PluginUninstalledWithName": "{0} desinstalatu da", "PluginUninstalledWithName": "{0} desinstalatu da",
"PluginInstalledWithName": "{0} instalatu da", "PluginInstalledWithName": "{0} instalatu da",
"Plugin": "Plugin",
"NotificationOptionVideoPlaybackStopped": "Bideoa geldituta", "NotificationOptionVideoPlaybackStopped": "Bideoa geldituta",
"NotificationOptionVideoPlayback": "Bideoa martxan", "NotificationOptionVideoPlayback": "Bideoa martxan",
"NotificationOptionUserLockedOut": "Erabiltzailea blokeatua", "NotificationOptionUserLockedOut": "Erabiltzailea blokeatua",
@@ -76,22 +90,37 @@
"NameInstallFailed": "{0} instalazioak huts egin du", "NameInstallFailed": "{0} instalazioak huts egin du",
"Music": "Musika", "Music": "Musika",
"MixedContent": "Eduki mistoa", "MixedContent": "Eduki mistoa",
"MessageServerConfigurationUpdated": "Zerbitzariaren konfigurazioa eguneratu da",
"MessageNamedServerConfigurationUpdatedWithValue": "Zerbitzariaren {0} konfigurazio atala eguneratu da",
"MessageApplicationUpdatedTo": "Jellyfin zerbitzaria {0}-ra eguneratu da",
"MessageApplicationUpdated": "Jellyfin zerbitzaria eguneratu da",
"Latest": "Azkena", "Latest": "Azkena",
"LabelRunningTimeValue": "Iraupena: {0}", "LabelRunningTimeValue": "Iraupena: {0}",
"LabelIpAddressValue": "IP helbidea: {0}", "LabelIpAddressValue": "IP helbidea: {0}",
"ItemRemovedWithName": "{0} liburutegitik kendu da",
"ItemAddedWithName": "{0} liburutegira gehitu da",
"HomeVideos": "Etxeko bideoak", "HomeVideos": "Etxeko bideoak",
"HeaderNextUp": "Hurrengoa", "HeaderNextUp": "Hurrengoa",
"HeaderLiveTV": "Zuzeneko TB", "HeaderLiveTV": "Zuzeneko TB",
"HeaderFavoriteSongs": "Gogoko abestiak",
"HeaderFavoriteShows": "Gogoko serieak", "HeaderFavoriteShows": "Gogoko serieak",
"HeaderFavoriteEpisodes": "Gogoko atalak", "HeaderFavoriteEpisodes": "Gogoko atalak",
"HeaderFavoriteArtists": "Gogoko artistak",
"HeaderFavoriteAlbums": "Gogoko albumak",
"Forced": "Behartuta", "Forced": "Behartuta",
"FailedLoginAttemptWithUserName": "{0}-tik saioa hasteak huts egin du", "FailedLoginAttemptWithUserName": "{0}-tik saioa hasteak huts egin du",
"External": "Kanpokoa", "External": "Kanpokoa",
"DeviceOnlineWithName": "{0} konektatu da",
"DeviceOfflineWithName": "{0} deskonektatu da",
"ChapterNameValue": "{0} Kapitulua", "ChapterNameValue": "{0} Kapitulua",
"CameraImageUploadedFrom": "{0}-tik kamera irudi berri bat igo da",
"AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da", "AuthenticationSucceededWithUserName": "{0} ongi autentifikatu da",
"Application": "Aplikazioa",
"AppDeviceValues": "App: {0}, Gailua: {1}", "AppDeviceValues": "App: {0}, Gailua: {1}",
"HearingImpaired": "Entzumen urritasuna", "HearingImpaired": "Entzumen urritasuna",
"ProviderValue": "Hornitzailea: {0}",
"TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.", "TaskKeyframeExtractorDescription": "Bideo fitxategietako fotograma gakoak ateratzen ditu HLS erreprodukzio-zerrenda zehatzagoak sortzeko. Zeregin honek denbora asko iraun dezake.",
"HeaderRecordingGroups": "Grabaketa taldeak",
"Inherit": "Oinordetu", "Inherit": "Oinordetu",
"TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.", "TaskOptimizeDatabaseDescription": "Datu-basea trinkotu eta bertatik espazioa askatzen du. Liburutegia eskaneatu ondoren edo datu-basean aldaketak egin ondoren ataza hau exekutatzeak errendimendua hobetu lezake.",
"TaskKeyframeExtractor": "Fotograma gakoen erauzgailua", "TaskKeyframeExtractor": "Fotograma gakoen erauzgailua",
@@ -101,12 +130,12 @@
"TaskDownloadMissingLyrics": "Deskargatu falta diren letrak", "TaskDownloadMissingLyrics": "Deskargatu falta diren letrak",
"TaskDownloadMissingLyricsDescription": "Deskargatu abestientzako letrak", "TaskDownloadMissingLyricsDescription": "Deskargatu abestientzako letrak",
"TaskExtractMediaSegments": "Multimedia segmentuen eskaneoa", "TaskExtractMediaSegments": "Multimedia segmentuen eskaneoa",
"TaskCleanCollectionsAndPlaylistsDescription": "Jada existitzen ez diren bildumak eta erreprodukzio-zerrendak kentzen ditu.",
"TaskCleanCollectionsAndPlaylists": "Garbitu bildumak eta erreprodukzio-zerrendak",
"TaskExtractMediaSegmentsDescription": "Media segmentuak atera edo lortzen ditu MediaSegment gaituta duten pluginetik.", "TaskExtractMediaSegmentsDescription": "Media segmentuak atera edo lortzen ditu MediaSegment gaituta duten pluginetik.",
"TaskMoveTrickplayImages": "Aldatu Trickplay irudien kokalekua", "TaskMoveTrickplayImages": "Aldatu Trickplay irudien kokalekua",
"TaskMoveTrickplayImagesDescription": "Lehendik dauden trickplay fitxategiak liburutegiaren ezarpenen arabera mugitzen dira.", "TaskMoveTrickplayImagesDescription": "Lehendik dauden trickplay fitxategiak liburutegiaren ezarpenen arabera mugitzen dira.",
"TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu.", "TaskAudioNormalizationDescription": "Audio normalizazio datuak lortzeko fitxategiak eskaneatzen ditu.",
"CleanupUserDataTaskDescription": "Gutxienez 90 egunez dagoeneko existitzen ez den multimediatik erabiltzaile-datu guztiak (ikusteko egoera, gogokoen egoera, etab.) garbitzen ditu.", "CleanupUserDataTaskDescription": "Gutxienez 90 egunez dagoeneko existitzen ez den multimediatik erabiltzaile-datu guztiak (ikusteko egoera, gogokoen egoera, etab.) garbitzen ditu.",
"CleanupUserDataTask": "Erabiltzaileen datuak garbitzeko zeregina", "CleanupUserDataTask": "Erabiltzaileen datuak garbitzeko zeregina"
"LyricDownloadFailureFromForItem": "Ezin izan dira {1}-ren letrak deskargatu {0}-tik",
"Original": "Jatorrizkoa"
} }

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "آلبوم‌ها",
"AppDeviceValues": "برنامه: {0} ، دستگاه: {1}", "AppDeviceValues": "برنامه: {0} ، دستگاه: {1}",
"Application": "برنامه",
"Artists": "هنرمندان", "Artists": "هنرمندان",
"AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد", "AuthenticationSucceededWithUserName": "{0} با موفقیت تایید اعتبار شد",
"Books": "کتاب‌ها", "Books": "کتاب‌ها",
"CameraImageUploadedFrom": "یک عکس جدید از دوربین ارسال شده است {0}",
"Channels": "کانالها",
"ChapterNameValue": "قسمت {0}", "ChapterNameValue": "قسمت {0}",
"Collections": "مجموعه‌ها", "Collections": "مجموعه‌ها",
"DeviceOfflineWithName": "ارتباط {0} قطع شد",
"DeviceOnlineWithName": "{0} متصل شد",
"FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود", "FailedLoginAttemptWithUserName": "تلاش برای ورود از {0} ناموفق بود",
"Favorites": "مورد علاقه‌ها", "Favorites": "مورد علاقه‌ها",
"Folders": "پوشه‌ها", "Folders": "پوشه‌ها",
"Genres": "ژانرها", "Genres": "ژانرها",
"HeaderAlbumArtists": "هنرمندان آلبوم",
"HeaderContinueWatching": "ادامه تماشا", "HeaderContinueWatching": "ادامه تماشا",
"HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه",
"HeaderFavoriteArtists": "هنرمندان مورد علاقه",
"HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه", "HeaderFavoriteEpisodes": "قسمت‌های مورد علاقه",
"HeaderFavoriteShows": "سریال‌های مورد علاقه", "HeaderFavoriteShows": "سریال‌های مورد علاقه",
"HeaderFavoriteSongs": "آهنگ‌های مورد علاقه",
"HeaderLiveTV": "پخش زنده", "HeaderLiveTV": "پخش زنده",
"HeaderNextUp": "قسمت بعدی", "HeaderNextUp": "قسمت بعدی",
"HeaderRecordingGroups": "گروه‌های ضبط",
"HomeVideos": "ویدیوهای خانگی", "HomeVideos": "ویدیوهای خانگی",
"Inherit": "به ارث برده", "Inherit": "به ارث برده",
"ItemAddedWithName": "{0} به کتابخانه افزوده شد",
"ItemRemovedWithName": "{0} از کتابخانه حذف شد",
"LabelIpAddressValue": "آدرس آی پی: {0}", "LabelIpAddressValue": "آدرس آی پی: {0}",
"LabelRunningTimeValue": "زمان اجرا: {0}", "LabelRunningTimeValue": "زمان اجرا: {0}",
"Latest": "جدیدترین‌ها", "Latest": "جدیدترین‌ها",
"MessageApplicationUpdated": "سرور Jellyfin بروزرسانی شد",
"MessageApplicationUpdatedTo": "سرور Jellyfin به نسخه {0} بروزرسانی شد",
"MessageNamedServerConfigurationUpdatedWithValue": "پکربندی بخش {0} سرور بروزرسانی شد",
"MessageServerConfigurationUpdated": "پیکربندی سرور بروزرسانی شد",
"MixedContent": "محتوای مخلوط", "MixedContent": "محتوای مخلوط",
"Movies": "فیلم ها", "Movies": "فیلم ها",
"Music": "موسیقی", "Music": "موسیقی",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "پخش ویدیو آغاز شد", "NotificationOptionVideoPlayback": "پخش ویدیو آغاز شد",
"NotificationOptionVideoPlaybackStopped": "پخش ویدیو متوقف شد", "NotificationOptionVideoPlaybackStopped": "پخش ویدیو متوقف شد",
"Photos": "عکس‌ها", "Photos": "عکس‌ها",
"Playlists": "لیست‌های پخش",
"Plugin": "افزونه",
"PluginInstalledWithName": "{0} نصب شد", "PluginInstalledWithName": "{0} نصب شد",
"PluginUninstalledWithName": "{0} حذف شد", "PluginUninstalledWithName": "{0} حذف شد",
"PluginUpdatedWithName": "{0} آپدیت شد", "PluginUpdatedWithName": "{0} آپدیت شد",
"ProviderValue": "ارائه دهنده: {0}",
"ScheduledTaskFailedWithName": "{0} شکست خورد", "ScheduledTaskFailedWithName": "{0} شکست خورد",
"ScheduledTaskStartedWithName": "{0} شروع شد",
"ServerNameNeedsToBeRestarted": "{0} نیاز به راه اندازی مجدد دارد",
"Shows": "سریال‌ها", "Shows": "سریال‌ها",
"Songs": "موسیقی‌ها",
"StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.", "StartupEmbyServerIsLoading": "سرور Jellyfin در حال بارگیری است. لطفا کمی بعد دوباره تلاش کنید.",
"SubtitleDownloadFailureFromForItem": "بارگیری زیرنویس برای {1} از {0} شکست خورد", "SubtitleDownloadFailureFromForItem": "بارگیری زیرنویس برای {1} از {0} شکست خورد",
"Sync": "همگام‌سازی",
"System": "سیستم",
"TvShows": "سریال‌های تلویزیونی", "TvShows": "سریال‌های تلویزیونی",
"User": "کاربر",
"UserCreatedWithName": "کاربر {0} ایجاد شد", "UserCreatedWithName": "کاربر {0} ایجاد شد",
"UserDeletedWithName": "کاربر {0} حذف شد", "UserDeletedWithName": "کاربر {0} حذف شد",
"UserDownloadingItemWithValues": "{0} در حال بارگیری {1} می‌باشد", "UserDownloadingItemWithValues": "{0} در حال بارگیری {1} می‌باشد",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "ارتباط {0} از {1} قطع شد", "UserOfflineFromDevice": "ارتباط {0} از {1} قطع شد",
"UserOnlineFromDevice": "{0} از {1} آنلاین می‌باشد", "UserOnlineFromDevice": "{0} از {1} آنلاین می‌باشد",
"UserPasswordChangedWithName": "گذرواژه برای کاربر {0} تغییر کرد", "UserPasswordChangedWithName": "گذرواژه برای کاربر {0} تغییر کرد",
"UserPolicyUpdatedWithName": "سیاست کاربری برای {0} بروزرسانی شد",
"UserStartedPlayingItemWithValues": "{0} در حال پخش {1} بر روی {2} است", "UserStartedPlayingItemWithValues": "{0} در حال پخش {1} بر روی {2} است",
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند", "UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
"ValueHasBeenAddedToLibrary": "{0} به کتابخانه‌ی رسانه‌ی شما افزوده شد",
"ValueSpecialEpisodeName": "ویژه - {0}",
"VersionNumber": "نسخه {0}", "VersionNumber": "نسخه {0}",
"TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.", "TaskCleanTranscodeDescription": "فایل‌های کدگذاری که قدیمی‌تر از یک روز هستند را حذف می‌کند.",
"TaskCleanTranscode": "پاکسازی مسیر کد گذاری", "TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
@@ -97,6 +126,8 @@
"HearingImpaired": "مشکل شنوایی", "HearingImpaired": "مشکل شنوایی",
"TaskRefreshTrickplayImages": "تولید تصاویر Trickplay", "TaskRefreshTrickplayImages": "تولید تصاویر Trickplay",
"TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه.", "TaskRefreshTrickplayImagesDescription": "تولید پیش‌نمایش های trickplay برای ویدیو های فعال شده در کتابخانه.",
"TaskCleanCollectionsAndPlaylists": "پاکسازی مجموعه ها و لیست پخش",
"TaskCleanCollectionsAndPlaylistsDescription": "موارد را از مجموعه ها و لیست پخش هایی که دیگر وجود ندارند حذف میکند.",
"TaskAudioNormalizationDescription": "بررسی فایل برای داده‌های نرمال کردن صدا.", "TaskAudioNormalizationDescription": "بررسی فایل برای داده‌های نرمال کردن صدا.",
"TaskDownloadMissingLyrics": "دانلود متن‌های ناموجود", "TaskDownloadMissingLyrics": "دانلود متن‌های ناموجود",
"TaskDownloadMissingLyricsDescription": "دانلود متن شعر‌ها", "TaskDownloadMissingLyricsDescription": "دانلود متن شعر‌ها",

View File

@@ -8,33 +8,57 @@
"Music": "Musiikki", "Music": "Musiikki",
"Movies": "Elokuvat", "Movies": "Elokuvat",
"MixedContent": "Sekalainen sisältö", "MixedContent": "Sekalainen sisältö",
"MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty",
"MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty",
"MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}",
"MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty",
"Latest": "Viimeisimmät", "Latest": "Viimeisimmät",
"LabelRunningTimeValue": "Kesto: {0}", "LabelRunningTimeValue": "Kesto: {0}",
"LabelIpAddressValue": "IP-osoite: {0}", "LabelIpAddressValue": "IP-osoite: {0}",
"ItemRemovedWithName": "{0} poistettiin kirjastosta",
"ItemAddedWithName": "{0} lisättiin kirjastoon",
"Inherit": "Peri", "Inherit": "Peri",
"HomeVideos": "Kotivideot", "HomeVideos": "Kotivideot",
"HeaderRecordingGroups": "Tallennusryhmät",
"HeaderNextUp": "Seuraavaksi", "HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Suosikkikappaleet",
"HeaderFavoriteShows": "Suosikkisarjat", "HeaderFavoriteShows": "Suosikkisarjat",
"HeaderFavoriteEpisodes": "Suosikkijaksot", "HeaderFavoriteEpisodes": "Suosikkijaksot",
"HeaderFavoriteArtists": "Suosikkiesittäjät",
"HeaderFavoriteAlbums": "Suosikkialbumit",
"HeaderContinueWatching": "Jatka katselua", "HeaderContinueWatching": "Jatka katselua",
"HeaderAlbumArtists": "Albumin esittäjät",
"Genres": "Tyylilajit", "Genres": "Tyylilajit",
"Folders": "Kansiot", "Folders": "Kansiot",
"Favorites": "Suosikit", "Favorites": "Suosikit",
"FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"", "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"",
"DeviceOnlineWithName": "{0} on yhdistetty",
"DeviceOfflineWithName": "{0} on katkaissut yhteyden",
"Collections": "Kokoelmat", "Collections": "Kokoelmat",
"ChapterNameValue": "Kappale {0}", "ChapterNameValue": "Kappale {0}",
"Channels": "Kanavat",
"CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}",
"Books": "Kirjat", "Books": "Kirjat",
"AuthenticationSucceededWithUserName": "{0} todennus onnistunut", "AuthenticationSucceededWithUserName": "{0} on todennettu",
"Artists": "Artistit", "Artists": "Esittäjät",
"Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}", "AppDeviceValues": "Sovellus: {0}, Laite: {1}",
"Albums": "Albumit",
"User": "Käyttäjä",
"System": "Järjestelmä",
"ScheduledTaskFailedWithName": "{0} epäonnistui", "ScheduledTaskFailedWithName": "{0} epäonnistui",
"PluginUpdatedWithName": "{0} päivitettiin", "PluginUpdatedWithName": "{0} päivitettiin",
"PluginInstalledWithName": "{0} asennettiin", "PluginInstalledWithName": "{0} asennettiin",
"Photos": "Valokuvat", "Photos": "Valokuvat",
"ScheduledTaskStartedWithName": "\"{0}\" käynnistetty",
"PluginUninstalledWithName": "{0} poistettiin", "PluginUninstalledWithName": "{0} poistettiin",
"Playlists": "Soittolistat",
"VersionNumber": "Versio {0}", "VersionNumber": "Versio {0}",
"ValueSpecialEpisodeName": "Erikoisjakso - {0}",
"ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon",
"UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"", "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"",
"UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"", "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"",
"UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty",
"UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu", "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu",
"UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"", "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"",
"UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"", "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"",
@@ -43,9 +67,14 @@
"UserDeletedWithName": "Käyttäjä {0} on poistettu", "UserDeletedWithName": "Käyttäjä {0} on poistettu",
"UserCreatedWithName": "Käyttäjä {0} on luotu", "UserCreatedWithName": "Käyttäjä {0} on luotu",
"TvShows": "Sarjat", "TvShows": "Sarjat",
"Sync": "Synkronointi",
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui", "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui",
"StartupEmbyServerIsLoading": "Jellyfin-palvelin on latautumassa. Yritä hetken kuluttua uudelleen.", "StartupEmbyServerIsLoading": "Jellyfin-palvelin on latautumassa. Yritä hetken kuluttua uudelleen.",
"Songs": "Kappaleet",
"Shows": "Sarjat", "Shows": "Sarjat",
"ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen",
"ProviderValue": "Lähde: {0}",
"Plugin": "Lisäosa",
"NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu", "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu",
"NotificationOptionVideoPlayback": "Videon toisto aloitettu", "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
"NotificationOptionUserLockedOut": "Käyttäjä on lukittu", "NotificationOptionUserLockedOut": "Käyttäjä on lukittu",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Äänenvoimakkuuden normalisointi",
"TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja.", "TaskAudioNormalizationDescription": "Etsii tiedostoista äänenvoimakkuuden normalisointitietoja.",
"TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka", "TaskDownloadMissingLyrics": "Lataa puuttuva lyriikka",

View File

@@ -1,7 +1,10 @@
{ {
"VersionNumber": "Bersyon {0}", "VersionNumber": "Bersyon {0}",
"ValueSpecialEpisodeName": "Espesyal - {0}",
"ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya",
"UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}", "UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
"UserStartedPlayingItemWithValues": "Si {0} ay nagpla-play ng {1} sa {2}", "UserStartedPlayingItemWithValues": "Si {0} ay nagpla-play ng {1} sa {2}",
"UserPolicyUpdatedWithName": "Ang user policy ay nai-update para kay {0}",
"UserPasswordChangedWithName": "Napalitan na ang password ni {0}", "UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
"UserOnlineFromDevice": "Si {0} ay naka-konekta galing sa {1}", "UserOnlineFromDevice": "Si {0} ay naka-konekta galing sa {1}",
"UserOfflineFromDevice": "Si {0} ay na-diskonekta galing sa {1}", "UserOfflineFromDevice": "Si {0} ay na-diskonekta galing sa {1}",
@@ -9,14 +12,23 @@
"UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}", "UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
"UserDeletedWithName": "Natanggal na is user {0}", "UserDeletedWithName": "Natanggal na is user {0}",
"UserCreatedWithName": "Nagawa na si user {0}", "UserCreatedWithName": "Nagawa na si user {0}",
"User": "User",
"TvShows": "Mga Palabas sa Telebisyon", "TvShows": "Mga Palabas sa Telebisyon",
"System": "Sistema",
"Sync": "Pag-sync",
"SubtitleDownloadFailureFromForItem": "Hindi nai-download ang subtitles {0} para sa {1}", "SubtitleDownloadFailureFromForItem": "Hindi nai-download ang subtitles {0} para sa {1}",
"StartupEmbyServerIsLoading": "Naglo-load ang Jellyfin Server. Mangyaring subukan ulit sandali.", "StartupEmbyServerIsLoading": "Naglo-load ang Jellyfin Server. Mangyaring subukan ulit sandali.",
"Songs": "Mga Kanta",
"Shows": "Mga Pelikula", "Shows": "Mga Pelikula",
"ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
"ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
"ScheduledTaskFailedWithName": "Hindi gumana ang {0}", "ScheduledTaskFailedWithName": "Hindi gumana ang {0}",
"ProviderValue": "Tagapagtustos: {0}",
"PluginUpdatedWithName": "Naiupdate na ang {0}", "PluginUpdatedWithName": "Naiupdate na ang {0}",
"PluginUninstalledWithName": "Naiuninstall na ang {0}", "PluginUninstalledWithName": "Naiuninstall na ang {0}",
"PluginInstalledWithName": "Nainstall na ang {0}", "PluginInstalledWithName": "Nainstall na ang {0}",
"Plugin": "Plugin",
"Playlists": "Mga Playlist",
"Photos": "Mga Larawan", "Photos": "Mga Larawan",
"NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula", "NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
"NotificationOptionVideoPlayback": "Nagsimula na ang pelikula", "NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
@@ -42,25 +54,42 @@
"Music": "Mga Kanta", "Music": "Mga Kanta",
"Movies": "Mga Pelikula", "Movies": "Mga Pelikula",
"MixedContent": "Halo-halong content", "MixedContent": "Halo-halong content",
"MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
"MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
"MessageApplicationUpdatedTo": "Ang bersyon ng Jellyfin Server ay naiupdate sa {0}",
"MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
"Latest": "Pinakabago", "Latest": "Pinakabago",
"LabelRunningTimeValue": "Oras: {0}", "LabelRunningTimeValue": "Oras: {0}",
"LabelIpAddressValue": "IP address: {0}", "LabelIpAddressValue": "IP address: {0}",
"ItemRemovedWithName": "Naitanggal ang {0} sa librerya",
"ItemAddedWithName": "Naidagdag ang {0} sa librerya",
"Inherit": "Manahin", "Inherit": "Manahin",
"HeaderRecordingGroups": "Pagtatalang Grupo",
"HeaderNextUp": "Susunod", "HeaderNextUp": "Susunod",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderFavoriteSongs": "Mga Paboritong Kanta",
"HeaderFavoriteShows": "Mga Paboritong Pelikula", "HeaderFavoriteShows": "Mga Paboritong Pelikula",
"HeaderFavoriteEpisodes": "Mga Paboritong Yugto", "HeaderFavoriteEpisodes": "Mga Paboritong Yugto",
"HeaderFavoriteArtists": "Mga Paboritong Artista",
"HeaderFavoriteAlbums": "Mga Paboritong Album",
"HeaderContinueWatching": "Magpatuloy sa Panonood", "HeaderContinueWatching": "Magpatuloy sa Panonood",
"HeaderAlbumArtists": "Mga Artista ng Album",
"Genres": "Mga Kategorya", "Genres": "Mga Kategorya",
"Folders": "Mga Folder", "Folders": "Mga Folder",
"Favorites": "Mga Paborito", "Favorites": "Mga Paborito",
"FailedLoginAttemptWithUserName": "Maling login galing kay/sa {0}", "FailedLoginAttemptWithUserName": "Maling login galing kay/sa {0}",
"DeviceOnlineWithName": "Nakakonekta si/ang {0}",
"DeviceOfflineWithName": "Nadiskonekta si/ang {0}",
"Collections": "Mga Koleksyon", "Collections": "Mga Koleksyon",
"ChapterNameValue": "Kabanata {0}", "ChapterNameValue": "Kabanata {0}",
"Channels": "Mga Channel",
"CameraImageUploadedFrom": "May bagong larawan na naupload galing sa/kay {0}",
"Books": "Mga Libro", "Books": "Mga Libro",
"AuthenticationSucceededWithUserName": "Napatunayan si/ang {0}", "AuthenticationSucceededWithUserName": "Napatunayan si/ang {0}",
"Artists": "Mga Artista", "Artists": "Mga Artista",
"Application": "Aplikasyon",
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}", "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
"Albums": "Mga Album",
"TaskRefreshLibrary": "Suriin and Librerya ng Medya", "TaskRefreshLibrary": "Suriin and Librerya ng Medya",
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.", "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.",
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata", "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",

View File

@@ -2,15 +2,17 @@
"Artists": "Listafólk", "Artists": "Listafólk",
"Collections": "Søvn", "Collections": "Søvn",
"Default": "Sjálvgildi", "Default": "Sjálvgildi",
"DeviceOfflineWithName": "{0} hevur slitið sambandið",
"External": "Ytri", "External": "Ytri",
"Genres": "Greinar", "Genres": "Greinar",
"Albums": "Album",
"AppDeviceValues": "App: {0}, Eind: {1}", "AppDeviceValues": "App: {0}, Eind: {1}",
"Application": "Nýtsluskipan",
"Books": "Bøkur", "Books": "Bøkur",
"Channels": "Rásir",
"ChapterNameValue": "Kapittul {0}", "ChapterNameValue": "Kapittul {0}",
"DeviceOnlineWithName": "{0} er sambundið",
"Favorites": "Yndis", "Favorites": "Yndis",
"Folders": "Mappur", "Folders": "Mappur",
"Forced": "Kravt", "Forced": "Kravt"
"FailedLoginAttemptWithUserName": "Miseydnað innritanarroynd frá {0}",
"HeaderFavoriteEpisodes": "Yndispartar",
"LabelIpAddressValue": "IP atsetur: {0}"
} }

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Albums",
"AppDeviceValues": "App : {0}, Appareil : {1}", "AppDeviceValues": "App : {0}, Appareil : {1}",
"Application": "Application",
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres", "Books": "Livres",
"CameraImageUploadedFrom": "Une nouvelle photo a été téléversée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}", "ChapterNameValue": "Chapitre {0}",
"Collections": "Collections", "Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
"FailedLoginAttemptWithUserName": "Tentative de connexion échouée par {0}", "FailedLoginAttemptWithUserName": "Tentative de connexion échouée par {0}",
"Favorites": "Favoris", "Favorites": "Favoris",
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artistes de l'album",
"HeaderContinueWatching": "Reprendre le visionnement", "HeaderContinueWatching": "Reprendre le visionnement",
"HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes favoris",
"HeaderFavoriteEpisodes": "Épisodes favoris", "HeaderFavoriteEpisodes": "Épisodes favoris",
"HeaderFavoriteShows": "Séries favorites", "HeaderFavoriteShows": "Séries favorites",
"HeaderFavoriteSongs": "Chansons favorites",
"HeaderLiveTV": "TV en direct", "HeaderLiveTV": "TV en direct",
"HeaderNextUp": "À Suivre", "HeaderNextUp": "À Suivre",
"HeaderRecordingGroups": "Groupes d'enregistrements",
"HomeVideos": "Vidéos personnelles", "HomeVideos": "Vidéos personnelles",
"Inherit": "Hérite", "Inherit": "Hérite",
"ItemAddedWithName": "{0} a été ajouté à la médiathèque",
"ItemRemovedWithName": "{0} a été supprimé de la médiathèque",
"LabelIpAddressValue": "Adresse IP : {0}", "LabelIpAddressValue": "Adresse IP : {0}",
"LabelRunningTimeValue": "Durée : {0}", "LabelRunningTimeValue": "Durée : {0}",
"Latest": "Plus récent", "Latest": "Plus récent",
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour vers la version {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
"MixedContent": "Contenu mixte", "MixedContent": "Contenu mixte",
"Movies": "Films", "Movies": "Films",
"Music": "Musique", "Music": "Musique",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Lecture vidéo démarrée", "NotificationOptionVideoPlayback": "Lecture vidéo démarrée",
"NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée",
"Photos": "Photos", "Photos": "Photos",
"Playlists": "Listes de lecture",
"Plugin": "Extension",
"PluginInstalledWithName": "{0} a été installé", "PluginInstalledWithName": "{0} a été installé",
"PluginUninstalledWithName": "{0} a été désinstallé", "PluginUninstalledWithName": "{0} a été désinstallé",
"PluginUpdatedWithName": "{0} a été mis à jour", "PluginUpdatedWithName": "{0} a été mis à jour",
"ProviderValue": "Fournisseur : {0}",
"ScheduledTaskFailedWithName": "{0} a échoué", "ScheduledTaskFailedWithName": "{0} a échoué",
"ScheduledTaskStartedWithName": "{0} a commencé",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
"Shows": "Séries", "Shows": "Séries",
"Songs": "Chansons",
"StartupEmbyServerIsLoading": "Serveur Jellyfin en cours de chargement. Réessayez dans quelques instants.", "StartupEmbyServerIsLoading": "Serveur Jellyfin en cours de chargement. Réessayez dans quelques instants.",
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
"Sync": "Synchroniser",
"System": "Système",
"TvShows": "Séries Télé", "TvShows": "Séries Télé",
"User": "Utilisateur",
"UserCreatedWithName": "L'utilisateur {0} a été créé", "UserCreatedWithName": "L'utilisateur {0} a été créé",
"UserDeletedWithName": "L'utilisateur {0} supprimé", "UserDeletedWithName": "L'utilisateur {0} supprimé",
"UserDownloadingItemWithValues": "{0} télécharge {1}", "UserDownloadingItemWithValues": "{0} télécharge {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} s'est déconnecté de {1}", "UserOfflineFromDevice": "{0} s'est déconnecté de {1}",
"UserOnlineFromDevice": "{0} s'est connecté de {1}", "UserOnlineFromDevice": "{0} s'est connecté de {1}",
"UserPasswordChangedWithName": "Le mot de passe de utilisateur {0} a été modifié", "UserPasswordChangedWithName": "Le mot de passe de utilisateur {0} a été modifié",
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
"UserStartedPlayingItemWithValues": "{0} joue {1} sur {2}", "UserStartedPlayingItemWithValues": "{0} joue {1} sur {2}",
"UserStoppedPlayingItemWithValues": "{0} a terminé la lecture de {1} sur {2}", "UserStoppedPlayingItemWithValues": "{0} a terminé la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TasksLibraryCategory": "Médiathèque", "TasksLibraryCategory": "Médiathèque",
"TasksMaintenanceCategory": "Entretien", "TasksMaintenanceCategory": "Entretien",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Normalisation audio",
"TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.", "TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.",
"TaskExtractMediaSegments": "Analyse des segments de média", "TaskExtractMediaSegments": "Analyse des segments de média",
@@ -106,7 +137,5 @@
"TaskMoveTrickplayImages": "Changer l'emplacement des images Trickplay", "TaskMoveTrickplayImages": "Changer l'emplacement des images Trickplay",
"TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.", "TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.",
"CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.", "CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.",
"CleanupUserDataTask": "Tâche de nettoyage des données utilisateur", "CleanupUserDataTask": "Tâche de nettoyage des données utilisateur"
"LyricDownloadFailureFromForItem": "Le téléchargement des paroles a échoué de {0} pour {1}",
"Original": "Original"
} }

View File

@@ -1,24 +1,41 @@
{ {
"Albums": "Albums",
"AppDeviceValues": "Application : {0}, Appareil : {1}", "AppDeviceValues": "Application : {0}, Appareil : {1}",
"Application": "Application",
"Artists": "Artistes", "Artists": "Artistes",
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès", "AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
"Books": "Livres", "Books": "Livres",
"CameraImageUploadedFrom": "Une photo a été téléchargée depuis {0}",
"Channels": "Chaînes",
"ChapterNameValue": "Chapitre {0}", "ChapterNameValue": "Chapitre {0}",
"Collections": "Collections", "Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
"FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}", "FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
"Favorites": "Favoris", "Favorites": "Favoris",
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artistes d'albums",
"HeaderContinueWatching": "Continuer de regarder", "HeaderContinueWatching": "Continuer de regarder",
"HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes préférés",
"HeaderFavoriteEpisodes": "Épisodes favoris", "HeaderFavoriteEpisodes": "Épisodes favoris",
"HeaderFavoriteShows": "Séries favorites", "HeaderFavoriteShows": "Séries favorites",
"HeaderFavoriteSongs": "Chansons préférées",
"HeaderLiveTV": "TV en direct", "HeaderLiveTV": "TV en direct",
"HeaderNextUp": "Prochain à venir", "HeaderNextUp": "Prochain à venir",
"HeaderRecordingGroups": "Groupes d'enregistrements",
"HomeVideos": "Vidéos personnelles", "HomeVideos": "Vidéos personnelles",
"Inherit": "Hériter", "Inherit": "Hériter",
"ItemAddedWithName": "{0} a été ajouté à la médiathèque",
"ItemRemovedWithName": "{0} a été supprimé de la médiathèque",
"LabelIpAddressValue": "Adresse IP : {0}", "LabelIpAddressValue": "Adresse IP : {0}",
"LabelRunningTimeValue": "Durée : {0}", "LabelRunningTimeValue": "Durée : {0}",
"Latest": "Derniers", "Latest": "Derniers",
"MessageApplicationUpdated": "Le serveur Jellyfin a été mis à jour",
"MessageApplicationUpdatedTo": "Le serveur Jellyfin a été mis à jour en version {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La configuration de la section {0} du serveur a été mise à jour",
"MessageServerConfigurationUpdated": "La configuration du serveur a été mise à jour",
"MixedContent": "Contenu mixte", "MixedContent": "Contenu mixte",
"Movies": "Films", "Movies": "Films",
"Music": "Musique", "Music": "Musique",
@@ -44,14 +61,23 @@
"NotificationOptionVideoPlayback": "Lecture vidéo démarrée", "NotificationOptionVideoPlayback": "Lecture vidéo démarrée",
"NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée", "NotificationOptionVideoPlaybackStopped": "Lecture vidéo arrêtée",
"Photos": "Photos", "Photos": "Photos",
"Playlists": "Listes de lecture",
"Plugin": "Extension",
"PluginInstalledWithName": "{0} a été installé", "PluginInstalledWithName": "{0} a été installé",
"PluginUninstalledWithName": "{0} a été désinstallé", "PluginUninstalledWithName": "{0} a été désinstallé",
"PluginUpdatedWithName": "{0} a été mis à jour", "PluginUpdatedWithName": "{0} a été mis à jour",
"ProviderValue": "Fournisseur : {0}",
"ScheduledTaskFailedWithName": "{0} a échoué", "ScheduledTaskFailedWithName": "{0} a échoué",
"ScheduledTaskStartedWithName": "{0} a démarré",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
"Shows": "Séries", "Shows": "Séries",
"Songs": "Chansons",
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.", "StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}", "SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
"Sync": "Synchroniser",
"System": "Système",
"TvShows": "Séries TV", "TvShows": "Séries TV",
"User": "Utilisateur",
"UserCreatedWithName": "L'utilisateur {0} a été créé", "UserCreatedWithName": "L'utilisateur {0} a été créé",
"UserDeletedWithName": "L'utilisateur {0} a été supprimé", "UserDeletedWithName": "L'utilisateur {0} a été supprimé",
"UserDownloadingItemWithValues": "{0} est en train de télécharger {1}", "UserDownloadingItemWithValues": "{0} est en train de télécharger {1}",
@@ -59,8 +85,11 @@
"UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}", "UserOfflineFromDevice": "{0} s'est déconnecté depuis {1}",
"UserOnlineFromDevice": "{0} s'est connecté depuis {1}", "UserOnlineFromDevice": "{0} s'est connecté depuis {1}",
"UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié", "UserPasswordChangedWithName": "Le mot de passe pour l'utilisateur {0} a été modifié",
"UserPolicyUpdatedWithName": "La politique de l'utilisateur a été mise à jour pour {0}",
"UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}", "UserStartedPlayingItemWithValues": "{0} est en train de lire {1} sur {2}",
"UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}", "UserStoppedPlayingItemWithValues": "{0} vient d'arrêter la lecture de {1} sur {2}",
"ValueHasBeenAddedToLibrary": "{0} a été ajouté à votre médiathèque",
"ValueSpecialEpisodeName": "Spécial - {0}",
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TasksChannelsCategory": "Chaînes en ligne", "TasksChannelsCategory": "Chaînes en ligne",
"TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur Internet en se basant sur la configuration des métadonnées.", "TaskDownloadMissingSubtitlesDescription": "Recherche les sous-titres manquants sur Internet en se basant sur la configuration des métadonnées.",
@@ -97,6 +126,8 @@
"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", "TaskAudioNormalization": "Normalisation audio",
"TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.", "TaskAudioNormalizationDescription": "Analyse les fichiers à la recherche de données de normalisation audio.",
"TaskDownloadMissingLyricsDescription": "Téléchargement des paroles des chansons", "TaskDownloadMissingLyricsDescription": "Téléchargement des paroles des chansons",
@@ -106,7 +137,5 @@
"TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.", "TaskExtractMediaSegmentsDescription": "Extrait ou obtient des segments de média à partir des plugins compatibles avec MediaSegment.",
"TaskMoveTrickplayImagesDescription": "Déplace les fichiers trickplay existants en fonction des paramètres de la bibliothèque.", "TaskMoveTrickplayImagesDescription": "Déplace les fichiers trickplay existants en fonction des paramètres de la bibliothèque.",
"CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.", "CleanupUserDataTaskDescription": "Nettoie toutes les données utilisateur (état de la montre, statut favori, etc.) des supports qui ne sont plus présents depuis au moins 90 jours.",
"CleanupUserDataTask": "Tâche de nettoyage des données utilisateur", "CleanupUserDataTask": "Tâche de nettoyage des données utilisateur"
"LyricDownloadFailureFromForItem": "Le téléchargement des paroles à échoué de {0} pour {1}",
"Original": "Original"
} }

View File

@@ -1,10 +1,15 @@
{ {
"Albums": "Albaim",
"Artists": "Ealaíontóirí", "Artists": "Ealaíontóirí",
"AuthenticationSucceededWithUserName": "D'éirigh le fíordheimhniú {0}", "AuthenticationSucceededWithUserName": "D'éirigh le fíordheimhniú {0}",
"Books": "Leabhair", "Books": "Leabhair",
"CameraImageUploadedFrom": "Uaslódáladh íomhá ceamara nua ó {0}",
"Channels": "Cainéil",
"ChapterNameValue": "Caibidil {0}", "ChapterNameValue": "Caibidil {0}",
"Collections": "Bailiúcháin", "Collections": "Bailiúcháin",
"Default": "Réamhshocrú", "Default": "Réamhshocrú",
"DeviceOfflineWithName": "Tá {0} dícheangailte",
"DeviceOnlineWithName": "Tá {0} nasctha",
"External": "Seachtrach", "External": "Seachtrach",
"FailedLoginAttemptWithUserName": "Theip ar iarracht logáil isteach ó {0}", "FailedLoginAttemptWithUserName": "Theip ar iarracht logáil isteach ó {0}",
"Favorites": "Ceanáin", "Favorites": "Ceanáin",
@@ -24,27 +29,41 @@
"TaskRefreshChannelsDescription": "Athnuachan eolas faoi chainéil idirlín.", "TaskRefreshChannelsDescription": "Athnuachan eolas faoi chainéil idirlín.",
"TaskOptimizeDatabase": "Bunachar sonraí a bharrfheabhsú", "TaskOptimizeDatabase": "Bunachar sonraí a bharrfheabhsú",
"TaskKeyframeExtractorDescription": "Baintear eochairfhrámaí as comhaid físe chun seinmliostaí HLS níos cruinne a chruthú. Féadfaidh an tasc seo a bheith ar siúl ar feadh i bhfad.", "TaskKeyframeExtractorDescription": "Baintear eochairfhrámaí as comhaid físe chun seinmliostaí HLS níos cruinne a chruthú. Féadfaidh an tasc seo a bheith ar siúl ar feadh i bhfad.",
"TaskCleanCollectionsAndPlaylistsDescription": "Baintear míreanna as bailiúcháin agus seinmliostaí nach ann dóibh a thuilleadh.",
"TaskDownloadMissingLyricsDescription": "Íosluchtaigh liricí do na hamhráin", "TaskDownloadMissingLyricsDescription": "Íosluchtaigh liricí do na hamhráin",
"TaskUpdatePluginsDescription": "Íoslódálann agus suiteálann nuashonruithe do bhreiseáin atá cumraithe le nuashonrú go huathoibríoch.", "TaskUpdatePluginsDescription": "Íoslódálann agus suiteálann nuashonruithe do bhreiseáin atá cumraithe le nuashonrú go huathoibríoch.",
"TaskDownloadMissingSubtitlesDescription": "Déanann sé cuardach ar an idirlíon le haghaidh fotheidil atá ar iarraidh bunaithe ar chumraíocht meiteashonraí.", "TaskDownloadMissingSubtitlesDescription": "Déanann sé cuardach ar an idirlíon le haghaidh fotheidil atá ar iarraidh bunaithe ar chumraíocht meiteashonraí.",
"TaskExtractMediaSegmentsDescription": "Sliocht nó faigheann codanna meán ó bhreiseáin chumasaithe MediaSegment.", "TaskExtractMediaSegmentsDescription": "Sliocht nó faigheann codanna meán ó bhreiseáin chumasaithe MediaSegment.",
"TaskCleanCollectionsAndPlaylists": "Glan suas bailiúcháin agus seinmliostaí",
"TaskOptimizeDatabaseDescription": "Comhdhlúthaíonn bunachar sonraí agus gearrtar spás saor in aisce. Má ritheann tú an tasc seo tar éis scanadh a dhéanamh ar an leabharlann nó athruithe eile a dhéanamh a thugann le tuiscint gur cheart go bhfeabhsófaí an fheidhmíocht.", "TaskOptimizeDatabaseDescription": "Comhdhlúthaíonn bunachar sonraí agus gearrtar spás saor in aisce. Má ritheann tú an tasc seo tar éis scanadh a dhéanamh ar an leabharlann nó athruithe eile a dhéanamh a thugann le tuiscint gur cheart go bhfeabhsófaí an fheidhmíocht.",
"TaskMoveTrickplayImagesDescription": "Bogtar comhaid trickplay atá ann cheana de réir socruithe na leabharlainne.", "TaskMoveTrickplayImagesDescription": "Bogtar comhaid trickplay atá ann cheana de réir socruithe na leabharlainne.",
"AppDeviceValues": "Aip: {0}, Gléas: {1}", "AppDeviceValues": "Aip: {0}, Gléas: {1}",
"Application": "Feidhmchlár",
"Folders": "Fillteáin", "Folders": "Fillteáin",
"Forced": "Éigean", "Forced": "Éigean",
"Genres": "Seánraí", "Genres": "Seánraí",
"HeaderAlbumArtists": "Ealaíontóirí albam",
"HeaderContinueWatching": "Leanúint ar aghaidh ag Breathnú", "HeaderContinueWatching": "Leanúint ar aghaidh ag Breathnú",
"HeaderFavoriteAlbums": "Albam is fearr leat",
"HeaderFavoriteArtists": "Ealaíontóirí is Fearr",
"HeaderFavoriteEpisodes": "Eipeasóid is fearr leat", "HeaderFavoriteEpisodes": "Eipeasóid is fearr leat",
"HeaderFavoriteShows": "Seónna is Fearr", "HeaderFavoriteShows": "Seónna is Fearr",
"HeaderFavoriteSongs": "Amhráin is fearr leat",
"HeaderLiveTV": "Teilifís beo", "HeaderLiveTV": "Teilifís beo",
"HeaderNextUp": "Ar Aghaidh Suas", "HeaderNextUp": "Ar Aghaidh Suas",
"HeaderRecordingGroups": "Grúpaí Taifeadta",
"HearingImpaired": "Lag éisteachta", "HearingImpaired": "Lag éisteachta",
"HomeVideos": "Físeáin Baile", "HomeVideos": "Físeáin Baile",
"Inherit": "Oidhreacht", "Inherit": "Oidhreacht",
"ItemAddedWithName": "Cuireadh {0} leis an leabharlann",
"ItemRemovedWithName": "Baineadh {0} den leabharlann",
"LabelIpAddressValue": "Seoladh IP: {0}", "LabelIpAddressValue": "Seoladh IP: {0}",
"LabelRunningTimeValue": "Am rite: {0}", "LabelRunningTimeValue": "Am rite: {0}",
"Latest": "Is déanaí", "Latest": "Is déanaí",
"MessageApplicationUpdated": "Tá Freastalaí Jellyfin nuashonraithe",
"MessageApplicationUpdatedTo": "Nuashonraíodh Freastalaí Jellyfin go {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "Nuashonraíodh an chuid cumraíochta freastalaí {0}",
"MessageServerConfigurationUpdated": "Nuashonraíodh cumraíocht an fhreastalaí",
"MixedContent": "Ábhar measctha", "MixedContent": "Ábhar measctha",
"Movies": "Scannáin", "Movies": "Scannáin",
"Music": "Ceol", "Music": "Ceol",
@@ -70,15 +89,24 @@
"NotificationOptionVideoPlayback": "Cuireadh tús le hathsheinm físe", "NotificationOptionVideoPlayback": "Cuireadh tús le hathsheinm físe",
"NotificationOptionVideoPlaybackStopped": "Cuireadh deireadh le hathsheinm físe", "NotificationOptionVideoPlaybackStopped": "Cuireadh deireadh le hathsheinm físe",
"Photos": "Grianghraif", "Photos": "Grianghraif",
"Playlists": "Seinmliostaí",
"Plugin": "Breiseán",
"PluginInstalledWithName": "Suiteáladh {0}", "PluginInstalledWithName": "Suiteáladh {0}",
"PluginUninstalledWithName": "Díshuiteáladh {0}", "PluginUninstalledWithName": "Díshuiteáladh {0}",
"PluginUpdatedWithName": "Nuashonraíodh {0}", "PluginUpdatedWithName": "Nuashonraíodh {0}",
"ProviderValue": "Soláthraí: {0}",
"ScheduledTaskFailedWithName": "Theip ar {0}", "ScheduledTaskFailedWithName": "Theip ar {0}",
"ScheduledTaskStartedWithName": "Thosaigh {0}",
"ServerNameNeedsToBeRestarted": "Ní mór {0} a atosú",
"Shows": "Seónna", "Shows": "Seónna",
"Songs": "Amhráin",
"StartupEmbyServerIsLoading": "Tá freastalaí Jellyfin á luchtú. Bain triail eile as gan mhoill.", "StartupEmbyServerIsLoading": "Tá freastalaí Jellyfin á luchtú. Bain triail eile as gan mhoill.",
"SubtitleDownloadFailureFromForItem": "Theip ar fhotheidil a íoslódáil ó {0} le haghaidh {1}", "SubtitleDownloadFailureFromForItem": "Theip ar fhotheidil a íoslódáil ó {0} le haghaidh {1}",
"Sync": "Sioncrónaigh",
"System": "Córas",
"TvShows": "Seónna Teilifíse", "TvShows": "Seónna Teilifíse",
"Undefined": "Neamhshainithe", "Undefined": "Neamhshainithe",
"User": "Úsáideoir",
"UserCreatedWithName": "Cruthaíodh úsáideoir {0}", "UserCreatedWithName": "Cruthaíodh úsáideoir {0}",
"UserDeletedWithName": "Scriosadh úsáideoir {0}", "UserDeletedWithName": "Scriosadh úsáideoir {0}",
"UserDownloadingItemWithValues": "Tá {0} á íoslódáil {1}", "UserDownloadingItemWithValues": "Tá {0} á íoslódáil {1}",
@@ -86,8 +114,11 @@
"UserOfflineFromDevice": "Tá {0} dícheangailte ó {1}", "UserOfflineFromDevice": "Tá {0} dícheangailte ó {1}",
"UserOnlineFromDevice": "Tá {0} ar líne ó {1}", "UserOnlineFromDevice": "Tá {0} ar líne ó {1}",
"UserPasswordChangedWithName": "Athraíodh pasfhocal don úsáideoir {0}", "UserPasswordChangedWithName": "Athraíodh pasfhocal don úsáideoir {0}",
"UserPolicyUpdatedWithName": "Nuashonraíodh polasaí úsáideora le haghaidh {0}",
"UserStartedPlayingItemWithValues": "Tá {0} ag seinnt {1} ar {2}", "UserStartedPlayingItemWithValues": "Tá {0} ag seinnt {1} ar {2}",
"UserStoppedPlayingItemWithValues": "Chríochnaigh {0} ag imirt {1} ar {2}", "UserStoppedPlayingItemWithValues": "Chríochnaigh {0} ag imirt {1} ar {2}",
"ValueHasBeenAddedToLibrary": "Cuireadh {0} le do leabharlann meán",
"ValueSpecialEpisodeName": "Speisialta - {0}",
"VersionNumber": "Leagan {0}", "VersionNumber": "Leagan {0}",
"TasksMaintenanceCategory": "Cothabháil", "TasksMaintenanceCategory": "Cothabháil",
"TasksLibraryCategory": "Leabharlann", "TasksLibraryCategory": "Leabharlann",
@@ -106,6 +137,5 @@
"TaskCleanTranscode": "Eolaire Transcode Glan", "TaskCleanTranscode": "Eolaire Transcode Glan",
"TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh", "TaskDownloadMissingSubtitles": "Íosluchtaigh fotheidil ar iarraidh",
"CleanupUserDataTask": "Tasc glantacháin sonraí úsáideora", "CleanupUserDataTask": "Tasc glantacháin sonraí úsáideora",
"CleanupUserDataTaskDescription": "Glanann sé gach sonraí úsáideora (stádas faire, stádas is fearr leat srl.) ó mheáin nach bhfuil i láthair a thuilleadh ar feadh 90 lá ar a laghad.", "CleanupUserDataTaskDescription": "Glanann sé gach sonraí úsáideora (stádas faire, stádas is fearr leat srl.) ó mheáin nach bhfuil i láthair a thuilleadh ar feadh 90 lá ar a laghad."
"Original": "Bunaidh"
} }

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