1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Integrate branch 'master' into feature/osd_chapter_selection

This commit is contained in:
TheMelmacian 2024-04-27 18:34:40 +02:00
commit dd6d278f15
133 changed files with 9460 additions and 5880 deletions

View file

@ -1,4 +1,8 @@
{ {
"$schema": "https://docs.renovatebot.com/renovate-schema.json", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["github>jellyfin/.github//renovate-presets/nodejs", ":semanticCommitsDisabled"] "extends": [
"github>jellyfin/.github//renovate-presets/nodejs",
":semanticCommitsDisabled",
":dependencyDashboard"
]
} }

View file

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'jellyfin/jellyfin-web' }} if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
steps: steps:
- uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0 - uses: eps1lon/actions-label-merge-conflict@e62d7a53ff8be8b97684bffb6cfbbf3fc1115e2e # v3.0.0
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

@ -18,7 +18,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
@ -41,7 +41,7 @@ jobs:
mv dist/config.tmp.json dist/config.json mv dist/config.tmp.json dist/config.json
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: jellyfin-web__prod name: jellyfin-web__prod
path: | path: |
@ -64,7 +64,7 @@ jobs:
echo $PR_SHA > PR_sha echo $PR_SHA > PR_sha
- name: Upload PR number as artifact - name: Upload PR number as artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
with: with:
name: PR_context name: PR_context
path: | path: |

View file

@ -19,16 +19,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 uses: github/codeql-action/init@8f596b4ae3cb3c588a5c46780b86dd53fef16c52 # v3.25.2
with: with:
languages: javascript languages: javascript
queries: +security-extended queries: +security-extended
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 uses: github/codeql-action/autobuild@8f596b4ae3cb3c588a5c46780b86dd53fef16c52 # v3.25.2
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@8a470fddafa5cbb6266ee11b37ef4d8aae19c571 # v3.24.6 uses: github/codeql-action/analyze@8f596b4ae3cb3c588a5c46780b86dd53fef16c52 # v3.25.2

View file

@ -18,7 +18,7 @@ jobs:
comment-id: ${{ github.event.comment.id }} comment-id: ${{ github.event.comment.id }}
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0

View file

@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with: with:
ref: ${{ github.event.pull_request.head.sha }} ref: ${{ github.event.pull_request.head.sha }}
@ -33,6 +33,6 @@ jobs:
- name: Run eslint - name: Run eslint
if: ${{ github.repository == 'jellyfin/jellyfin-web' }} if: ${{ github.repository == 'jellyfin/jellyfin-web' }}
uses: CatChen/eslint-suggestion-action@8fb7db4e235f7af9fc434349a124034b681d99a3 # v3.1.3 uses: CatChen/eslint-suggestion-action@34e2a6c4193eba18a7a20710b5ae37850fc984c3 # v3.1.5
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -20,7 +20,7 @@ jobs:
steps: steps:
- name: Download workflow artifact - name: Download workflow artifact
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
with: with:
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}
name: jellyfin-web__prod name: jellyfin-web__prod
@ -47,7 +47,7 @@ jobs:
steps: steps:
- name: Get PR context - name: Get PR context
uses: dawidd6/action-download-artifact@71072fbb1229e1317f1a8de6b04206afb461bd67 # v3.1.2 uses: dawidd6/action-download-artifact@09f2f74827fd3a8607589e5ad7f9398816f540fe # v3.1.4
id: pr_context id: pr_context
with: with:
run_id: ${{ github.event.workflow_run.id }} run_id: ${{ github.event.workflow_run.id }}

View file

@ -17,7 +17,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
@ -41,7 +41,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
@ -62,7 +62,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
@ -86,7 +86,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
@ -107,7 +107,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Setup node environment - name: Setup node environment
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2

View file

@ -16,7 +16,7 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
with: with:
ref: master ref: master
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
@ -35,7 +35,7 @@ jobs:
echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV echo "JF_SDK_VERSION=${VERSION}" >> $GITHUB_ENV
- name: Open a pull request - name: Open a pull request
uses: peter-evans/create-pull-request@a4f52f8033a6168103c2538976c07b467e8163bc # v6.0.1 uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
with: with:
token: ${{ secrets.JF_BOT_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}} commit-message: Update @jellyfin/sdk to ${{env.JF_SDK_VERSION}}

View file

@ -79,9 +79,13 @@
- [Kevin Tan (Valius)](https://github.com/valius) - [Kevin Tan (Valius)](https://github.com/valius)
- [Rasmus Krämer](https://github.com/rasmuslos) - [Rasmus Krämer](https://github.com/rasmuslos)
- [ntarelix](https://github.com/ntarelix) - [ntarelix](https://github.com/ntarelix)
- [btopherjohnson](https://github.com/btopherjohnson)
- [András Maróy](https://github.com/andrasmaroy) - [András Maróy](https://github.com/andrasmaroy)
- [Chris-Codes-It](https://github.com/Chris-Codes-It) - [Chris-Codes-It](https://github.com/Chris-Codes-It)
- [Vedant](https://github.com/viktory36) - [Vedant](https://github.com/viktory36)
- [GeorgeH005](https://github.com/GeorgeH005)
- [JPUC1143](https://github.com/Jpuc1143)
- [David Angel](https://github.com/davidangel)
## Emby Contributors ## Emby Contributors

8844
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,80 +5,81 @@
"repository": "https://github.com/jellyfin/jellyfin-web", "repository": "https://github.com/jellyfin/jellyfin-web",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"devDependencies": { "devDependencies": {
"@babel/core": "7.23.7", "@babel/core": "7.24.3",
"@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-class-properties": "7.18.6",
"@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6",
"@babel/plugin-transform-modules-umd": "7.23.3", "@babel/plugin-transform-modules-umd": "7.24.1",
"@babel/preset-env": "7.23.8", "@babel/preset-env": "7.24.3",
"@babel/preset-react": "7.23.3", "@babel/preset-react": "7.24.1",
"@types/escape-html": "1.0.4", "@types/escape-html": "1.0.4",
"@types/loadable__component": "5.13.9", "@types/loadable__component": "5.13.9",
"@types/lodash-es": "4.17.12", "@types/lodash-es": "4.17.12",
"@types/markdown-it": "13.0.7", "@types/markdown-it": "13.0.7",
"@types/react": "17.0.75", "@types/react": "17.0.79",
"@types/react-dom": "17.0.25", "@types/react-dom": "17.0.25",
"@types/sortablejs": "1.15.8", "@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "5.62.0", "@typescript-eslint/eslint-plugin": "5.62.0",
"@typescript-eslint/parser": "5.62.0", "@typescript-eslint/parser": "5.62.0",
"@uupaa/dynamic-import-polyfill": "1.0.2", "@uupaa/dynamic-import-polyfill": "1.0.2",
"autoprefixer": "10.4.17", "autoprefixer": "10.4.19",
"babel-loader": "9.1.3", "babel-loader": "9.1.3",
"babel-plugin-dynamic-import-polyfill": "1.0.0", "babel-plugin-dynamic-import-polyfill": "1.0.0",
"clean-webpack-plugin": "4.0.0", "clean-webpack-plugin": "4.0.0",
"confusing-browser-globals": "1.0.11", "confusing-browser-globals": "1.0.11",
"copy-webpack-plugin": "12.0.2", "copy-webpack-plugin": "12.0.2",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"css-loader": "6.9.1", "css-loader": "6.10.0",
"cssnano": "6.0.5", "cssnano": "6.1.2",
"es-check": "7.1.1", "es-check": "7.1.1",
"eslint": "8.56.0", "eslint": "8.57.0",
"eslint-plugin-compat": "4.2.0", "eslint-plugin-compat": "4.2.0",
"eslint-plugin-eslint-comments": "3.2.0", "eslint-plugin-eslint-comments": "3.2.0",
"eslint-plugin-import": "2.29.1", "eslint-plugin-import": "2.29.1",
"eslint-plugin-jsx-a11y": "6.8.0", "eslint-plugin-jsx-a11y": "6.8.0",
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.34.1",
"eslint-plugin-react-hooks": "4.6.0", "eslint-plugin-react-hooks": "4.6.0",
"eslint-plugin-sonarjs": "0.23.0", "eslint-plugin-sonarjs": "0.24.0",
"expose-loader": "4.1.0", "expose-loader": "4.1.0",
"fork-ts-checker-webpack-plugin": "9.0.2", "fork-ts-checker-webpack-plugin": "9.0.2",
"html-loader": "4.2.0", "html-loader": "4.2.0",
"html-webpack-plugin": "5.6.0", "html-webpack-plugin": "5.6.0",
"jsdom": "23.2.0", "jsdom": "23.2.0",
"mini-css-extract-plugin": "2.7.7", "mini-css-extract-plugin": "2.8.1",
"postcss": "8.4.33", "postcss": "8.4.38",
"postcss-loader": "7.3.4", "postcss-loader": "7.3.4",
"postcss-preset-env": "9.3.0", "postcss-preset-env": "9.5.2",
"postcss-scss": "4.0.9", "postcss-scss": "4.0.9",
"sass": "1.70.0", "sass": "1.72.0",
"sass-loader": "13.3.3", "sass-loader": "13.3.3",
"source-map-loader": "4.0.2", "source-map-loader": "4.0.2",
"speed-measure-webpack-plugin": "1.5.0", "speed-measure-webpack-plugin": "1.5.0",
"style-loader": "3.3.4", "style-loader": "3.3.4",
"stylelint": "15.11.0", "stylelint": "15.11.0",
"stylelint-config-rational-order": "0.1.2", "stylelint-config-rational-order": "0.1.2",
"stylelint-no-browser-hacks": "1.2.1", "stylelint-no-browser-hacks": "1.3.0",
"stylelint-order": "6.0.4", "stylelint-order": "6.0.4",
"stylelint-scss": "5.3.2", "stylelint-scss": "5.3.2",
"ts-loader": "9.5.1", "ts-loader": "9.5.1",
"typescript": "5.3.3", "typescript": "5.4.3",
"vitest": "1.3.0", "vitest": "1.4.0",
"webpack": "5.89.0", "webpack": "5.91.0",
"webpack-bundle-analyzer": "4.10.1", "webpack-bundle-analyzer": "4.10.1",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1", "webpack-dev-server": "4.15.2",
"webpack-merge": "5.10.0", "webpack-merge": "5.10.0",
"worker-loader": "3.0.8" "worker-loader": "3.0.8"
}, },
"dependencies": { "dependencies": {
"@emotion/react": "11.11.4", "@emotion/react": "11.11.4",
"@emotion/styled": "11.11.0", "@emotion/styled": "11.11.0",
"@fontsource/noto-sans": "5.0.18", "@fontsource/noto-sans": "5.0.21",
"@fontsource/noto-sans-hk": "5.0.17", "@fontsource/noto-sans-hk": "5.0.18",
"@fontsource/noto-sans-jp": "5.0.17", "@fontsource/noto-sans-jp": "5.0.18",
"@fontsource/noto-sans-kr": "5.0.17", "@fontsource/noto-sans-kr": "5.0.18",
"@fontsource/noto-sans-sc": "5.0.17", "@fontsource/noto-sans-sc": "5.0.18",
"@fontsource/noto-sans-tc": "5.0.17", "@fontsource/noto-sans-tc": "5.0.18",
"@jellyfin/sdk": "0.0.0-unstable.202403180216", "@jellyfin/libass-wasm": "4.2.1",
"@jellyfin/sdk": "0.0.0-unstable.202404101900",
"@loadable/component": "5.16.3", "@loadable/component": "5.16.3",
"@mui/icons-material": "5.15.11", "@mui/icons-material": "5.15.11",
"@mui/material": "5.15.11", "@mui/material": "5.15.11",
@ -91,25 +92,23 @@
"blurhash": "2.0.5", "blurhash": "2.0.5",
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
"classnames": "2.5.1", "classnames": "2.5.1",
"core-js": "3.35.1", "core-js": "3.36.1",
"date-fns": "2.30.0", "date-fns": "2.30.0",
"dompurify": "3.0.1", "dompurify": "3.0.1",
"epubjs": "0.3.93", "epubjs": "0.3.93",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"event-target-polyfill": "github:ThaUnknown/event-target-polyfill",
"fast-text-encoding": "1.0.6", "fast-text-encoding": "1.0.6",
"flv.js": "1.6.2", "flv.js": "1.6.2",
"headroom.js": "0.12.0", "headroom.js": "0.12.0",
"history": "5.3.0", "history": "5.3.0",
"hls.js": "1.5.7", "hls.js": "1.5.7",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"jassub": "1.7.15",
"jellyfin-apiclient": "1.11.0", "jellyfin-apiclient": "1.11.0",
"jquery": "3.7.1", "jquery": "3.7.1",
"jstree": "3.3.16", "jstree": "3.3.16",
"libarchive.js": "1.3.0", "libarchive.js": "1.3.0",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"markdown-it": "14.0.0", "markdown-it": "14.1.0",
"material-design-icons-iconfont": "6.7.0", "material-design-icons-iconfont": "6.7.0",
"native-promise-only": "0.8.1", "native-promise-only": "0.8.1",
"pdfjs-dist": "3.11.174", "pdfjs-dist": "3.11.174",
@ -117,12 +116,12 @@
"react-blurhash": "0.3.0", "react-blurhash": "0.3.0",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-lazy-load-image-component": "1.6.0", "react-lazy-load-image-component": "1.6.0",
"react-router-dom": "6.21.3", "react-router-dom": "6.22.3",
"resize-observer-polyfill": "1.5.1", "resize-observer-polyfill": "1.5.1",
"screenfull": "6.0.2", "screenfull": "6.0.2",
"sortablejs": "1.15.2", "sortablejs": "1.15.2",
"swiper": "11.0.5", "swiper": "11.0.7",
"usehooks-ts": "2.14.0", "usehooks-ts": "2.16.0",
"webcomponents.js": "0.7.24", "webcomponents.js": "0.7.24",
"whatwg-fetch": "3.6.20" "whatwg-fetch": "3.6.20"
}, },
@ -146,8 +145,8 @@
"start": "npm run serve", "start": "npm run serve",
"serve": "webpack serve --config webpack.dev.js", "serve": "webpack serve --config webpack.dev.js",
"build:analyze": "cross-env NODE_ENV=\"production\" webpack --config webpack.analyze.js", "build:analyze": "cross-env NODE_ENV=\"production\" webpack --config webpack.analyze.js",
"build:development": "cross-env NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.dev.js", "build:development": "webpack --config webpack.dev.js",
"build:production": "cross-env NODE_ENV=\"production\" NODE_OPTIONS=\"--max_old_space_size=6144\" webpack --config webpack.prod.js", "build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js",
"build:check": "tsc --noEmit", "build:check": "tsc --noEmit",
"escheck": "es-check", "escheck": "es-check",
"lint": "eslint \"./\"", "lint": "eslint \"./\"",

1
src/apiclient.d.ts vendored
View file

@ -76,6 +76,7 @@ declare module 'jellyfin-apiclient' {
accessToken(): string; accessToken(): string;
addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>; addMediaPath(virtualFolderName: string, mediaPath: string, networkSharePath: string, refreshLibrary?: boolean): Promise<void>;
addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>; addVirtualFolder(name: string, type?: string, refreshLibrary?: boolean, libraryOptions?: any): Promise<void>;
ajax(request: any): Promise<any>;
appName(): string; appName(): string;
appVersion(): string; appVersion(): string;
authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>; authenticateUserByName(name: string, password: string): Promise<AuthenticationResult>;

View file

@ -8,7 +8,6 @@ import ButtonElement from '../../../../elements/ButtonElement';
import CheckBoxElement from '../../../../elements/CheckBoxElement'; import CheckBoxElement from '../../../../elements/CheckBoxElement';
import SelectElement from '../../../../elements/SelectElement'; import SelectElement from '../../../../elements/SelectElement';
import InputElement from '../../../../elements/InputElement'; import InputElement from '../../../../elements/InputElement';
import LinkTrickplayAcceleration from '../../../../components/dashboard/playback/trickplay/LinkTrickplayAcceleration';
import loading from '../../../../components/loading/loading'; import loading from '../../../../components/loading/loading';
import toast from '../../../../components/toast/toast'; import toast from '../../../../components/toast/toast';
import ServerConnections from '../../../../components/ServerConnections'; import ServerConnections from '../../../../components/ServerConnections';
@ -31,6 +30,7 @@ const PlaybackTrickplay: FunctionComponent = () => {
} }
(page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options.EnableHwAcceleration; (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked = options.EnableHwAcceleration;
(page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked = options.EnableHwEncoding;
(page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = options.ScanBehavior; (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value = options.ScanBehavior;
(page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = options.ProcessPriority; (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value = options.ProcessPriority;
(page.querySelector('#txtInterval') as HTMLInputElement).value = options.Interval; (page.querySelector('#txtInterval') as HTMLInputElement).value = options.Interval;
@ -76,6 +76,7 @@ const PlaybackTrickplay: FunctionComponent = () => {
const options = config.TrickplayOptions; const options = config.TrickplayOptions;
options.EnableHwAcceleration = (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked; options.EnableHwAcceleration = (page.querySelector('.chkEnableHwAcceleration') as HTMLInputElement).checked;
options.EnableHwEncoding = (page.querySelector('.chkEnableHwEncoding') as HTMLInputElement).checked;
options.ScanBehavior = (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value as TrickplayScanBehavior; options.ScanBehavior = (page.querySelector('#selectScanBehavior') as HTMLSelectElement).value as TrickplayScanBehavior;
options.ProcessPriority = (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value as ProcessPriorityClass; options.ProcessPriority = (page.querySelector('#selectProcessPriority') as HTMLSelectElement).value as ProcessPriorityClass;
options.Interval = Math.max(1, parseInt((page.querySelector('#txtInterval') as HTMLInputElement).value || '10000', 10)); options.Interval = Math.max(1, parseInt((page.querySelector('#txtInterval') as HTMLInputElement).value || '10000', 10));
@ -154,12 +155,16 @@ const PlaybackTrickplay: FunctionComponent = () => {
className='chkEnableHwAcceleration' className='chkEnableHwAcceleration'
title='LabelTrickplayAccel' title='LabelTrickplayAccel'
/> />
<div className='fieldDescription checkboxFieldDescription'> </div>
<LinkTrickplayAcceleration <div className='checkboxContainer checkboxContainer-withDescription'>
title='LabelTrickplayAccelHelp' <CheckBoxElement
href='#/dashboard/playback/transcoding' className='chkEnableHwEncoding'
className='button-link' title='LabelTrickplayAccelEncoding'
/> />
<div className='fieldDescription checkboxFieldDescription'>
<div className='fieldDescription'>
{globalize.translate('LabelTrickplayAccelEncodingHelp')}
</div>
</div> </div>
</div> </div>

View file

@ -6,7 +6,7 @@ import escapeHTML from 'escape-html';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
import LibraryMenu from '../../../../scripts/libraryMenu'; import LibraryMenu from '../../../../scripts/libraryMenu';
import AccessScheduleList from '../../../../components/dashboard/users/AccessScheduleList'; import AccessScheduleList from '../../../../components/dashboard/users/AccessScheduleList';
import BlockedTagList from '../../../../components/dashboard/users/BlockedTagList'; import TagList from '../../../../components/dashboard/users/TagList';
import ButtonElement from '../../../../elements/ButtonElement'; import ButtonElement from '../../../../elements/ButtonElement';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer'; import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs'; import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
@ -16,6 +16,8 @@ import { getParameterByName } from '../../../../utils/url';
import CheckBoxElement from '../../../../elements/CheckBoxElement'; import CheckBoxElement from '../../../../elements/CheckBoxElement';
import SelectElement from '../../../../elements/SelectElement'; import SelectElement from '../../../../elements/SelectElement';
import Page from '../../../../components/Page'; import Page from '../../../../components/Page';
import prompt from '../../../../components/prompt/prompt';
import ServerConnections from 'components/ServerConnections';
type UnratedItem = { type UnratedItem = {
name: string; name: string;
@ -23,12 +25,44 @@ type UnratedItem = {
checkedAttribute: string checkedAttribute: string
}; };
function handleSaveUser(
page: HTMLDivElement,
getSchedulesFromPage: () => AccessSchedule[],
getAllowedTagsFromPage: () => string[],
getBlockedTagsFromPage: () => string[],
onSaveComplete: () => void
) {
return (user: UserDto) => {
const userId = user.Id;
const userPolicy = user.Policy;
if (!userId || !userPolicy) {
throw new Error('Unexpected null user id or policy');
}
const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10);
userPolicy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating;
userPolicy.BlockUnratedItems = Array.prototype.filter
.call(page.querySelectorAll('.chkUnratedItem'), i => i.checked)
.map(i => i.getAttribute('data-itemtype'));
userPolicy.AccessSchedules = getSchedulesFromPage();
userPolicy.AllowedTags = getAllowedTagsFromPage();
userPolicy.BlockedTags = getBlockedTagsFromPage();
ServerConnections.getCurrentApiClientAsync()
.then(apiClient => apiClient.updateUserPolicy(userId, userPolicy))
.then(() => onSaveComplete())
.catch(err => {
console.error('[userparentalcontrol] failed to update user policy', err);
});
};
}
const UserParentalControl: FunctionComponent = () => { const UserParentalControl: FunctionComponent = () => {
const [ userName, setUserName ] = useState(''); const [ userName, setUserName ] = useState('');
const [ parentalRatings, setParentalRatings ] = useState<ParentalRating[]>([]); const [ parentalRatings, setParentalRatings ] = useState<ParentalRating[]>([]);
const [ unratedItems, setUnratedItems ] = useState<UnratedItem[]>([]); const [ unratedItems, setUnratedItems ] = useState<UnratedItem[]>([]);
const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]); const [ accessSchedules, setAccessSchedules ] = useState<AccessSchedule[]>([]);
const [ blockedTags, setBlockedTags ] = useState([]); const [ allowedTags, setAllowedTags ] = useState<string[]>([]);
const [ blockedTags, setBlockedTags ] = useState<string[]>([]);
const element = useRef<HTMLDivElement>(null); const element = useRef<HTMLDivElement>(null);
@ -106,7 +140,28 @@ const UserParentalControl: FunctionComponent = () => {
blockUnratedItems.dispatchEvent(new CustomEvent('create')); blockUnratedItems.dispatchEvent(new CustomEvent('create'));
}, []); }, []);
const loadBlockedTags = useCallback((tags) => { const loadAllowedTags = useCallback((tags: string[]) => {
const page = element.current;
if (!page) {
console.error('Unexpected null reference');
return;
}
setAllowedTags(tags);
const allowedTagsElem = page.querySelector('.allowedTags') as HTMLDivElement;
for (const btnDeleteTag of allowedTagsElem.querySelectorAll('.btnDeleteTag')) {
btnDeleteTag.addEventListener('click', function () {
const tag = btnDeleteTag.getAttribute('data-tag');
const newTags = tags.filter(t => t !== tag);
loadAllowedTags(newTags);
});
}
}, []);
const loadBlockedTags = useCallback((tags: string[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -121,9 +176,7 @@ const UserParentalControl: FunctionComponent = () => {
for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) { for (const btnDeleteTag of blockedTagsElem.querySelectorAll('.btnDeleteTag')) {
btnDeleteTag.addEventListener('click', function () { btnDeleteTag.addEventListener('click', function () {
const tag = btnDeleteTag.getAttribute('data-tag'); const tag = btnDeleteTag.getAttribute('data-tag');
const newTags = tags.filter(function (t: string) { const newTags = tags.filter(t => t !== tag);
return t != tag;
});
loadBlockedTags(newTags); loadBlockedTags(newTags);
}); });
} }
@ -145,15 +198,13 @@ const UserParentalControl: FunctionComponent = () => {
btnDelete.addEventListener('click', function () { btnDelete.addEventListener('click', function () {
const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10); const index = parseInt(btnDelete.getAttribute('data-index') ?? '0', 10);
schedules.splice(index, 1); schedules.splice(index, 1);
const newindex = schedules.filter(function (i: number) { const newindex = schedules.filter((i: number) => i != index);
return i != index;
});
renderAccessSchedule(newindex); renderAccessSchedule(newindex);
}); });
} }
}, []); }, []);
const loadUser = useCallback((user, allParentalRatings) => { const loadUser = useCallback((user: UserDto, allParentalRatings: ParentalRating[]) => {
const page = element.current; const page = element.current;
if (!page) { if (!page) {
@ -161,34 +212,33 @@ const UserParentalControl: FunctionComponent = () => {
return; return;
} }
setUserName(user.Name); setUserName(user.Name || '');
LibraryMenu.setTitle(user.Name); LibraryMenu.setTitle(user.Name);
loadUnratedItems(user); loadUnratedItems(user);
loadBlockedTags(user.Policy.BlockedTags); loadAllowedTags(user.Policy?.AllowedTags || []);
loadBlockedTags(user.Policy?.BlockedTags || []);
populateRatings(allParentalRatings); populateRatings(allParentalRatings);
let ratingValue = ''; let ratingValue = '';
if (user.Policy?.MaxParentalRating) {
if (user.Policy.MaxParentalRating != null) { allParentalRatings.forEach(rating => {
for (let i = 0, length = allParentalRatings.length; i < length; i++) { if (rating.Value && user.Policy?.MaxParentalRating && user.Policy.MaxParentalRating >= rating.Value) {
const rating = allParentalRatings[i]; ratingValue = `${rating.Value}`;
if (user.Policy.MaxParentalRating >= rating.Value) {
ratingValue = rating.Value;
}
} }
});
} }
(page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value = ratingValue; (page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value = ratingValue;
if (user.Policy.IsAdministrator) { if (user.Policy?.IsAdministrator) {
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide'); (page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.add('hide');
} else { } else {
(page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.remove('hide'); (page.querySelector('.accessScheduleSection') as HTMLDivElement).classList.remove('hide');
} }
renderAccessSchedule(user.Policy.AccessSchedules || []); renderAccessSchedule(user.Policy?.AccessSchedules || []);
loading.hide(); loading.hide();
}, [loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]); }, [loadAllowedTags, loadBlockedTags, loadUnratedItems, populateRatings, renderAccessSchedule]);
const loadData = useCallback(() => { const loadData = useCallback(() => {
loading.show(); loading.show();
@ -212,32 +262,6 @@ const UserParentalControl: FunctionComponent = () => {
loadData(); loadData();
const onSaveComplete = () => {
loading.hide();
toast(globalize.translate('SettingsSaved'));
};
const saveUser = (user: UserDto) => {
if (!user.Id || !user.Policy) {
throw new Error('Unexpected null user id or policy');
}
const parentalRating = parseInt((page.querySelector('#selectMaxParentalRating') as HTMLSelectElement).value, 10);
user.Policy.MaxParentalRating = Number.isNaN(parentalRating) ? null : parentalRating;
user.Policy.BlockUnratedItems = Array.prototype.filter.call(page.querySelectorAll('.chkUnratedItem'), function (i) {
return i.checked;
}).map(function (i) {
return i.getAttribute('data-itemtype');
});
user.Policy.AccessSchedules = getSchedulesFromPage();
user.Policy.BlockedTags = getBlockedTagsFromPage();
window.ApiClient.updateUserPolicy(user.Id, user.Policy).then(function () {
onSaveComplete();
}).catch(err => {
console.error('[userparentalcontrol] failed to update user policy', err);
});
};
const showSchedulePopup = (schedule: AccessSchedule, index: number) => { const showSchedulePopup = (schedule: AccessSchedule, index: number) => {
schedule = schedule || {}; schedule = schedule || {};
import('../../../../components/accessSchedule/accessSchedule').then(({ default: accessschedule }) => { import('../../../../components/accessSchedule/accessSchedule').then(({ default: accessschedule }) => {
@ -270,6 +294,27 @@ const UserParentalControl: FunctionComponent = () => {
}) as AccessSchedule[]; }) as AccessSchedule[];
}; };
const getAllowedTagsFromPage = () => {
return Array.prototype.map.call(page.querySelectorAll('.allowedTag'), function (elem) {
return elem.getAttribute('data-tag');
}) as string[];
};
const showAllowedTagPopup = () => {
prompt({
label: globalize.translate('LabelTag')
}).then(function (value) {
const tags = getAllowedTagsFromPage();
if (tags.indexOf(value) == -1) {
tags.push(value);
loadAllowedTags(tags);
}
}).catch(() => {
// prompt closed
});
};
const getBlockedTagsFromPage = () => { const getBlockedTagsFromPage = () => {
return Array.prototype.map.call(page.querySelectorAll('.blockedTag'), function (elem) { return Array.prototype.map.call(page.querySelectorAll('.blockedTag'), function (elem) {
return elem.getAttribute('data-tag'); return elem.getAttribute('data-tag');
@ -277,7 +322,6 @@ const UserParentalControl: FunctionComponent = () => {
}; };
const showBlockedTagPopup = () => { const showBlockedTagPopup = () => {
import('../../../../components/prompt/prompt').then(({ default: prompt }) => {
prompt({ prompt({
label: globalize.translate('LabelTag') label: globalize.translate('LabelTag')
}).then(function (value) { }).then(function (value) {
@ -290,11 +334,15 @@ const UserParentalControl: FunctionComponent = () => {
}).catch(() => { }).catch(() => {
// prompt closed // prompt closed
}); });
}).catch(err => {
console.error('[userparentalcontrol] failed to load prompt', err);
});
}; };
const onSaveComplete = () => {
loading.hide();
toast(globalize.translate('SettingsSaved'));
};
const saveUser = handleSaveUser(page, getSchedulesFromPage, getAllowedTagsFromPage, getBlockedTagsFromPage, onSaveComplete);
const onSubmit = (e: Event) => { const onSubmit = (e: Event) => {
loading.show(); loading.show();
const userId = getParameterByName('userId'); const userId = getParameterByName('userId');
@ -318,12 +366,16 @@ const UserParentalControl: FunctionComponent = () => {
}, -1); }, -1);
}); });
(page.querySelector('#btnAddAllowedTag') as HTMLButtonElement).addEventListener('click', function () {
showAllowedTagPopup();
});
(page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', function () { (page.querySelector('#btnAddBlockedTag') as HTMLButtonElement).addEventListener('click', function () {
showBlockedTagPopup(); showBlockedTagPopup();
}); });
(page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', onSubmit); (page.querySelector('.userParentalControlForm') as HTMLFormElement).addEventListener('submit', onSubmit);
}, [loadBlockedTags, loadData, renderAccessSchedule]); }, [loadAllowedTags, loadBlockedTags, loadData, renderAccessSchedule]);
const optionMaxParentalRating = () => { const optionMaxParentalRating = () => {
let content = ''; let content = '';
@ -378,6 +430,30 @@ const UserParentalControl: FunctionComponent = () => {
</div> </div>
</div> </div>
<br /> <br />
<div className='verticalSection' style={{ marginBottom: '2em' }}>
<SectionTitleContainer
SectionClassName='detailSectionHeader'
title={globalize.translate('LabelAllowContentWithTags')}
isBtnVisible={true}
btnId='btnAddAllowedTag'
btnClassName='fab submit sectionTitleButton'
btnTitle='Add'
btnIcon='add'
isLinkVisible={false}
/>
<div className='fieldDescription'>
{globalize.translate('AllowContentWithTagsHelp')}
</div>
<div className='allowedTags' style={{ marginTop: '.5em' }}>
{allowedTags?.map(tag => {
return <TagList
key={tag}
tag={tag}
tagType='allowedTag'
/>;
})}
</div>
</div>
<div className='verticalSection' style={{ marginBottom: '2em' }}> <div className='verticalSection' style={{ marginBottom: '2em' }}>
<SectionTitleContainer <SectionTitleContainer
SectionClassName='detailSectionHeader' SectionClassName='detailSectionHeader'
@ -389,11 +465,15 @@ const UserParentalControl: FunctionComponent = () => {
btnIcon='add' btnIcon='add'
isLinkVisible={false} isLinkVisible={false}
/> />
<div className='fieldDescription'>
{globalize.translate('BlockContentWithTagsHelp')}
</div>
<div className='blockedTags' style={{ marginTop: '.5em' }}> <div className='blockedTags' style={{ marginTop: '.5em' }}>
{blockedTags.map(tag => { {blockedTags.map(tag => {
return <BlockedTagList return <TagList
key={tag} key={tag}
tag={tag} tag={tag}
tagType='blockedTag'
/>; />;
})} })}
</div> </div>

View file

@ -10,6 +10,11 @@ $mui-bp-xl: 1536px;
position: relative; position: relative;
} }
// Ensure the footer renders over the drawer
.appfooter {
z-index: 1201 !important; // mui drawer uses z-index 1200
}
// Hide some items from the user "settings" page that are in the drawer // Hide some items from the user "settings" page that are in the drawer
#myPreferencesMenuPage { #myPreferencesMenuPage {
.lnkQuickConnectPreferences, .lnkQuickConnectPreferences,
@ -36,3 +41,8 @@ $mui-bp-xl: 1536px;
padding-top: 3.25rem !important; padding-top: 3.25rem !important;
} }
} }
// Fix backdrop position on mobile item details page
.layout-mobile .itemBackdrop {
margin-top: 0 !important;
}

View file

@ -8,5 +8,6 @@ export const ASYNC_USER_ROUTES: AsyncRoute[] = [
{ path: 'movies.html', page: 'movies', type: AsyncRouteType.Experimental }, { path: 'movies.html', page: 'movies', type: AsyncRouteType.Experimental },
{ path: 'tv.html', page: 'shows', type: AsyncRouteType.Experimental }, { path: 'tv.html', page: 'shows', type: AsyncRouteType.Experimental },
{ path: 'music.html', page: 'music', type: AsyncRouteType.Experimental }, { path: 'music.html', page: 'music', type: AsyncRouteType.Experimental },
{ path: 'livetv.html', page: 'livetv', type: AsyncRouteType.Experimental } { path: 'livetv.html', page: 'livetv', type: AsyncRouteType.Experimental },
{ path: 'mypreferencesdisplay.html', page: 'user/display', type: AsyncRouteType.Experimental }
]; ];

View file

@ -13,6 +13,12 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'list', controller: 'list',
view: 'list.html' view: 'list.html'
} }
}, {
path: 'lyrics',
pageProps: {
controller: 'lyrics',
view: 'lyrics.html'
}
}, { }, {
path: 'mypreferencesmenu.html', path: 'mypreferencesmenu.html',
pageProps: { pageProps: {
@ -25,12 +31,6 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'user/controls/index', controller: 'user/controls/index',
view: 'user/controls/index.html' view: 'user/controls/index.html'
} }
}, {
path: 'mypreferencesdisplay.html',
pageProps: {
controller: 'user/display/index',
view: 'user/display/index.html'
}
}, { }, {
path: 'mypreferenceshome.html', path: 'mypreferenceshome.html',
pageProps: { pageProps: {

View file

@ -79,7 +79,10 @@ const Music: FC = () => {
<PageTabContent <PageTabContent
key={`${currentTab.viewType} - ${libraryId}`} key={`${currentTab.viewType} - ${libraryId}`}
currentTab={currentTab} currentTab={currentTab}
parentId={libraryId} parentId={
// Playlists exist outside of the scope of the library
currentTab.viewType === LibraryTab.Playlists ? undefined : libraryId
}
/> />
</Page> </Page>
); );

View file

@ -0,0 +1,203 @@
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import React, { Fragment } from 'react';
import { appHost } from 'components/apphost';
import { useApi } from 'hooks/useApi';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
import { useScreensavers } from './hooks/useScreensavers';
import { useServerThemes } from './hooks/useServerThemes';
interface DisplayPreferencesProps {
onChange: (event: SelectChangeEvent | React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function DisplayPreferences({ onChange, values }: Readonly<DisplayPreferencesProps>) {
const { user } = useApi();
const { screensavers } = useScreensavers();
const { themes } = useServerThemes();
return (
<Stack spacing={3}>
<Typography variant='h2'>{globalize.translate('Display')}</Typography>
{ appHost.supports('displaymode') && (
<FormControl fullWidth>
<InputLabel id='display-settings-layout-label'>{globalize.translate('LabelDisplayMode')}</InputLabel>
<Select
aria-describedby='display-settings-layout-description'
inputProps={{
name: 'layout'
}}
labelId='display-settings-layout-label'
onChange={onChange}
value={values.layout}
>
<MenuItem value='auto'>{globalize.translate('Auto')}</MenuItem>
<MenuItem value='desktop'>{globalize.translate('Desktop')}</MenuItem>
<MenuItem value='mobile'>{globalize.translate('Mobile')}</MenuItem>
<MenuItem value='tv'>{globalize.translate('TV')}</MenuItem>
<MenuItem value='experimental'>{globalize.translate('Experimental')}</MenuItem>
</Select>
<FormHelperText component={Stack} id='display-settings-layout-description'>
<span>{globalize.translate('DisplayModeHelp')}</span>
<span>{globalize.translate('LabelPleaseRestart')}</span>
</FormHelperText>
</FormControl>
) }
{ themes.length > 0 && (
<FormControl fullWidth>
<InputLabel id='display-settings-theme-label'>{globalize.translate('LabelTheme')}</InputLabel>
<Select
inputProps={{
name: 'theme'
}}
labelId='display-settings-theme-label'
onChange={onChange}
value={values.theme}
>
{ ...themes.map(({ id, name }) => (
<MenuItem key={id} value={id}>{name}</MenuItem>
))}
</Select>
</FormControl>
) }
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-disable-css-description'
control={
<Checkbox
checked={values.disableCustomCss}
onChange={onChange}
/>
}
label={globalize.translate('DisableCustomCss')}
name='disableCustomCss'
/>
<FormHelperText id='display-settings-disable-css-description'>
{globalize.translate('LabelDisableCustomCss')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-custom-css-description'
value={values.customCss}
label={globalize.translate('LabelCustomCss')}
multiline
name='customCss'
onChange={onChange}
/>
<FormHelperText id='display-settings-custom-css-description'>
{globalize.translate('LabelLocalCustomCss')}
</FormHelperText>
</FormControl>
{ themes.length > 0 && user?.Policy?.IsAdministrator && (
<FormControl fullWidth>
<InputLabel id='display-settings-dashboard-theme-label'>{globalize.translate('LabelDashboardTheme')}</InputLabel>
<Select
inputProps={{
name: 'dashboardTheme'
}}
labelId='display-settings-dashboard-theme-label'
onChange={ onChange }
value={ values.dashboardTheme }
>
{ ...themes.map(({ id, name }) => (
<MenuItem key={ id } value={ id }>{ name }</MenuItem>
)) }
</Select>
</FormControl>
) }
{ screensavers.length > 0 && appHost.supports('screensaver') && (
<Fragment>
<FormControl fullWidth>
<InputLabel id='display-settings-screensaver-label'>{globalize.translate('LabelScreensaver')}</InputLabel>
<Select
inputProps={{
name: 'screensaver'
}}
labelId='display-settings-screensaver-label'
onChange={onChange}
value={values.screensaver}
>
{ ...screensavers.map(({ id, name }) => (
<MenuItem key={id} value={id}>{name}</MenuItem>
))}
</Select>
</FormControl>
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-screensaver-interval-description'
value={values.screensaverInterval}
inputProps={{
inputMode: 'numeric',
max: '3600',
min: '1',
pattern: '[0-9]',
required: true,
step: '1',
type: 'number'
}}
label={globalize.translate('LabelBackdropScreensaverInterval')}
name='screensaverInterval'
onChange={onChange}
/>
<FormHelperText id='display-settings-screensaver-interval-description'>
{globalize.translate('LabelBackdropScreensaverIntervalHelp')}
</FormHelperText>
</FormControl>
</Fragment>
) }
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-faster-animations-description'
control={
<Checkbox
checked={values.enableFasterAnimation}
onChange={onChange}
/>
}
label={globalize.translate('EnableFasterAnimations')}
name='enableFasterAnimation'
/>
<FormHelperText id='display-settings-faster-animations-description'>
{globalize.translate('EnableFasterAnimationsHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-blurhash-description'
control={
<Checkbox
checked={values.enableBlurHash}
onChange={onChange}
/>
}
label={globalize.translate('EnableBlurHash')}
name='enableBlurHash'
/>
<FormHelperText id='display-settings-blurhash-description'>
{globalize.translate('EnableBlurHashHelp')}
</FormHelperText>
</FormControl>
</Stack>
);
}

View file

@ -0,0 +1,40 @@
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
interface ItemDetailPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function ItemDetailPreferences({ onChange, values }: Readonly<ItemDetailPreferencesProps>) {
return (
<Stack spacing={2}>
<Typography variant='h2'>{globalize.translate('ItemDetails')}</Typography>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-item-details-banner-description'
control={
<Checkbox
checked={values.enableItemDetailsBanner}
onChange={onChange}
/>
}
label={globalize.translate('EnableDetailsBanner')}
name='enableItemDetailsBanner'
/>
<FormHelperText id='display-settings-item-details-banner-description'>
{globalize.translate('EnableDetailsBannerHelp')}
</FormHelperText>
</FormControl>
</Stack>
);
}

View file

@ -0,0 +1,114 @@
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
interface LibraryPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function LibraryPreferences({ onChange, values }: Readonly<LibraryPreferencesProps>) {
return (
<Stack spacing={3}>
<Typography variant='h2'>{globalize.translate('HeaderLibraries')}</Typography>
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-lib-pagesize-description'
inputProps={{
type: 'number',
inputMode: 'numeric',
max: '1000',
min: '0',
pattern: '[0-9]',
required: true,
step: '1'
}}
value={values.libraryPageSize}
label={globalize.translate('LabelLibraryPageSize')}
name='libraryPageSize'
onChange={onChange}
/>
<FormHelperText id='display-settings-lib-pagesize-description'>
{globalize.translate('LabelLibraryPageSizeHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-backdrops-description'
control={
<Checkbox
checked={values.enableLibraryBackdrops}
onChange={onChange}
/>
}
label={globalize.translate('Backdrops')}
name='enableLibraryBackdrops'
/>
<FormHelperText id='display-settings-lib-backdrops-description'>
{globalize.translate('EnableBackdropsHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-theme-songs-description'
control={
<Checkbox
checked={values.enableLibraryThemeSongs}
onChange={onChange}
/>
}
label={globalize.translate('ThemeSongs')}
name='enableLibraryThemeSongs'
/>
<FormHelperText id='display-settings-lib-theme-songs-description'>
{globalize.translate('EnableThemeSongsHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-lib-theme-videos-description'
control={
<Checkbox
checked={values.enableLibraryThemeVideos}
onChange={onChange}
/>
}
label={globalize.translate('ThemeVideos')}
name='enableLibraryThemeVideos'
/>
<FormHelperText id='display-settings-lib-theme-videos-description'>
{globalize.translate('EnableThemeVideosHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-show-missing-episodes-description'
control={
<Checkbox
checked={values.displayMissingEpisodes}
onChange={onChange}
/>
}
label={globalize.translate('DisplayMissingEpisodesWithinSeasons')}
name='displayMissingEpisodes'
/>
<FormHelperText id='display-settings-show-missing-episodes-description'>
{globalize.translate('DisplayMissingEpisodesWithinSeasonsHelp')}
</FormHelperText>
</FormControl>
</Stack>
);
}

View file

@ -0,0 +1,80 @@
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import Link from '@mui/material/Link';
import MenuItem from '@mui/material/MenuItem';
import Select, { SelectChangeEvent } from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import React from 'react';
import { appHost } from 'components/apphost';
import datetime from 'scripts/datetime';
import globalize from 'scripts/globalize';
import { DATE_LOCALE_OPTIONS, LANGUAGE_OPTIONS } from './constants';
import { DisplaySettingsValues } from './types';
interface LocalizationPreferencesProps {
onChange: (event: SelectChangeEvent) => void;
values: DisplaySettingsValues;
}
export function LocalizationPreferences({ onChange, values }: Readonly<LocalizationPreferencesProps>) {
if (!appHost.supports('displaylanguage') && !datetime.supportsLocalization()) {
return null;
}
return (
<Stack spacing={3}>
<Typography variant='h2'>{globalize.translate('Localization')}</Typography>
{ appHost.supports('displaylanguage') && (
<FormControl fullWidth>
<InputLabel id='display-settings-language-label'>{globalize.translate('LabelDisplayLanguage')}</InputLabel>
<Select
aria-describedby='display-settings-language-description'
inputProps={{
name: 'language'
}}
labelId='display-settings-language-label'
onChange={onChange}
value={values.language}
>
{ ...LANGUAGE_OPTIONS.map(({ value, label }) => (
<MenuItem key={value } value={value}>{ label }</MenuItem>
))}
</Select>
<FormHelperText component={Stack} id='display-settings-language-description'>
<span>{globalize.translate('LabelDisplayLanguageHelp')}</span>
{ appHost.supports('externallinks') && (
<Link
href='https://github.com/jellyfin/jellyfin'
rel='noopener noreferrer'
target='_blank'
>
{globalize.translate('LearnHowYouCanContribute')}
</Link>
) }
</FormHelperText>
</FormControl>
) }
{ datetime.supportsLocalization() && (
<FormControl fullWidth>
<InputLabel id='display-settings-locale-label'>{globalize.translate('LabelDateTimeLocale')}</InputLabel>
<Select
inputProps={{
name: 'dateTimeLocale'
}}
labelId='display-settings-locale-label'
onChange={onChange}
value={values.dateTimeLocale}
>
{...DATE_LOCALE_OPTIONS.map(({ value, label }) => (
<MenuItem key={value} value={value}>{label}</MenuItem>
))}
</Select>
</FormControl>
) }
</Stack>
);
}

View file

@ -0,0 +1,80 @@
import Checkbox from '@mui/material/Checkbox';
import FormControl from '@mui/material/FormControl';
import FormControlLabel from '@mui/material/FormControlLabel';
import FormHelperText from '@mui/material/FormHelperText';
import Stack from '@mui/material/Stack';
import TextField from '@mui/material/TextField';
import Typography from '@mui/material/Typography';
import React from 'react';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from './types';
interface NextUpPreferencesProps {
onChange: (event: React.SyntheticEvent) => void;
values: DisplaySettingsValues;
}
export function NextUpPreferences({ onChange, values }: Readonly<NextUpPreferencesProps>) {
return (
<Stack spacing={3}>
<Typography variant='h2'>{globalize.translate('NextUp')}</Typography>
<FormControl fullWidth>
<TextField
aria-describedby='display-settings-max-days-next-up-description'
value={values.maxDaysForNextUp}
inputProps={{
type: 'number',
inputMode: 'numeric',
max: '1000',
min: '0',
pattern: '[0-9]',
required: true,
step: '1'
}}
label={globalize.translate('LabelMaxDaysForNextUp')}
name='maxDaysForNextUp'
onChange={onChange}
/>
<FormHelperText id='display-settings-max-days-next-up-description'>
{globalize.translate('LabelMaxDaysForNextUpHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-next-up-rewatching-description'
control={
<Checkbox
checked={values.enableRewatchingInNextUp}
onChange={onChange}
/>
}
label={globalize.translate('EnableRewatchingNextUp')}
name='enableRewatchingInNextUp'
/>
<FormHelperText id='display-settings-next-up-rewatching-description'>
{globalize.translate('EnableRewatchingNextUpHelp')}
</FormHelperText>
</FormControl>
<FormControl fullWidth>
<FormControlLabel
aria-describedby='display-settings-next-up-images-description'
control={
<Checkbox
checked={values.episodeImagesInNextUp}
onChange={onChange}
/>
}
label={globalize.translate('UseEpisodeImagesInNextUp')}
name='episodeImagesInNextUp'
/>
<FormHelperText id='display-settings-next-up-images-description'>
{globalize.translate('UseEpisodeImagesInNextUpHelp')}
</FormHelperText>
</FormControl>
</Stack>
);
}

View file

@ -0,0 +1,79 @@
import globalize from 'scripts/globalize';
export const LANGUAGE_OPTIONS = [
{ value: 'auto', label: globalize.translate('Auto') },
{ value: 'af', label: 'Afrikaans' },
{ value: 'ar', label: 'العربية' },
{ value: 'be-BY', label: 'Беларуская' },
{ value: 'bg-BG', label: 'Български' },
{ value: 'bn_BD', label: 'বাংলা (বাংলাদেশ)' },
{ value: 'ca', label: 'Català' },
{ value: 'cs', label: 'Čeština' },
{ value: 'cy', label: 'Cymraeg' },
{ value: 'da', label: 'Dansk' },
{ value: 'de', label: 'Deutsch' },
{ value: 'el', label: 'Ελληνικά' },
{ value: 'en-GB', label: 'English (United Kingdom)' },
{ value: 'en-US', label: 'English' },
{ value: 'eo', label: 'Esperanto' },
{ value: 'es', label: 'Español' },
{ value: 'es_419', label: 'Español americano' },
{ value: 'es-AR', label: 'Español (Argentina)' },
{ value: 'es_DO', label: 'Español (Dominicana)' },
{ value: 'es-MX', label: 'Español (México)' },
{ value: 'et', label: 'Eesti' },
{ value: 'eu', label: 'Euskara' },
{ value: 'fa', label: 'فارسی' },
{ value: 'fi', label: 'Suomi' },
{ value: 'fil', label: 'Filipino' },
{ value: 'fr', label: 'Français' },
{ value: 'fr-CA', label: 'Français (Canada)' },
{ value: 'gl', label: 'Galego' },
{ value: 'gsw', label: 'Schwiizerdütsch' },
{ value: 'he', label: 'עִבְרִית' },
{ value: 'hi-IN', label: 'हिन्दी' },
{ value: 'hr', label: 'Hrvatski' },
{ value: 'hu', label: 'Magyar' },
{ value: 'id', label: 'Bahasa Indonesia' },
{ value: 'is-IS', label: 'Íslenska' },
{ value: 'it', label: 'Italiano' },
{ value: 'ja', label: '日本語' },
{ value: 'kk', label: 'Qazaqşa' },
{ value: 'ko', label: '한국어' },
{ value: 'lt-LT', label: 'Lietuvių' },
{ value: 'lv', label: 'Latviešu' },
{ value: 'mk', label: 'Македонски' },
{ value: 'ml', label: 'മലയാളം' },
{ value: 'mr', label: 'मराठी' },
{ value: 'ms', label: 'Bahasa Melayu' },
{ value: 'nb', label: 'Norsk bokmål' },
{ value: 'ne', label: 'नेपाली' },
{ value: 'nl', label: 'Nederlands' },
{ value: 'nn', label: 'Norsk nynorsk' },
{ value: 'pa', label: 'ਪੰਜਾਬੀ' },
{ value: 'pl', label: 'Polski' },
{ value: 'pr', label: 'Pirate' },
{ value: 'pt', label: 'Português' },
{ value: 'pt-BR', label: 'Português (Brasil)' },
{ value: 'pt-PT', label: 'Português (Portugal)' },
{ value: 'ro', label: 'Românește' },
{ value: 'ru', label: 'Русский' },
{ value: 'sk', label: 'Slovenčina' },
{ value: 'sl-SI', label: 'Slovenščina' },
{ value: 'sq', label: 'Shqip' },
{ value: 'sr', label: 'Српски' },
{ value: 'sv', label: 'Svenska' },
{ value: 'ta', label: 'தமிழ்' },
{ value: 'te', label: 'తెలుగు' },
{ value: 'th', label: 'ภาษาไทย' },
{ value: 'tr', label: 'Türkçe' },
{ value: 'uk', label: 'Українська' },
{ value: 'ur_PK', label: ' اُردُو' },
{ value: 'vi', label: 'Tiếng Việt' },
{ value: 'zh-CN', label: '汉语 (简化字)' },
{ value: 'zh-TW', label: '漢語 (繁体字)' },
{ value: 'zh-HK', label: '廣東話 (香港)' }
];
// NOTE: Option `Euskara` (eu) does not exist in legacy date locale options.
export const DATE_LOCALE_OPTIONS = LANGUAGE_OPTIONS.filter(({ value }) => value !== 'eu');

View file

@ -0,0 +1,46 @@
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import toast from 'components/toast/toast';
import globalize from 'scripts/globalize';
import { DisplaySettingsValues } from '../types';
import { useDisplaySettings } from './useDisplaySettings';
export function useDisplaySettingForm() {
const [urlParams] = useSearchParams();
const {
displaySettings,
loading,
saveDisplaySettings
} = useDisplaySettings({ userId: urlParams.get('userId') });
const [formValues, setFormValues] = useState<DisplaySettingsValues>();
useEffect(() => {
if (!loading && displaySettings && !formValues) {
setFormValues(displaySettings);
}
}, [formValues, loading, displaySettings]);
const updateField = useCallback(({ name, value }) => {
if (formValues) {
setFormValues({
...formValues,
[name]: value
});
}
}, [formValues, setFormValues]);
const submitChanges = useCallback(async () => {
if (formValues) {
await saveDisplaySettings(formValues);
toast(globalize.translate('SettingsSaved'));
}
}, [formValues, saveDisplaySettings]);
return {
loading,
values: formValues,
submitChanges,
updateField
};
}

View file

@ -0,0 +1,159 @@
import { UserDto } from '@jellyfin/sdk/lib/generated-client';
import { ApiClient } from 'jellyfin-apiclient';
import { useCallback, useEffect, useState } from 'react';
import { appHost } from 'components/apphost';
import layoutManager from 'components/layoutManager';
import { useApi } from 'hooks/useApi';
import themeManager from 'scripts/themeManager';
import { currentSettings, UserSettings } from 'scripts/settings/userSettings';
import { DisplaySettingsValues } from '../types';
interface UseDisplaySettingsParams {
userId?: string | null;
}
export function useDisplaySettings({ userId }: UseDisplaySettingsParams) {
const [loading, setLoading] = useState(true);
const [userSettings, setUserSettings] = useState<UserSettings>();
const [displaySettings, setDisplaySettings] = useState<DisplaySettingsValues>();
const { __legacyApiClient__, user: currentUser } = useApi();
useEffect(() => {
if (!userId || !currentUser || !__legacyApiClient__) {
return;
}
setLoading(true);
void (async () => {
const loadedSettings = await loadDisplaySettings({ api: __legacyApiClient__, currentUser, userId });
setDisplaySettings(loadedSettings.displaySettings);
setUserSettings(loadedSettings.userSettings);
setLoading(false);
})();
return () => {
setLoading(false);
};
}, [__legacyApiClient__, currentUser, userId]);
const saveSettings = useCallback(async (newSettings: DisplaySettingsValues) => {
if (!userId || !userSettings || !__legacyApiClient__) {
return;
}
return saveDisplaySettings({
api: __legacyApiClient__,
newDisplaySettings: newSettings,
userSettings,
userId
});
}, [__legacyApiClient__, userSettings, userId]);
return {
displaySettings,
loading,
saveDisplaySettings: saveSettings
};
}
interface LoadDisplaySettingsParams {
currentUser: UserDto;
userId?: string;
api: ApiClient;
}
async function loadDisplaySettings({
currentUser,
userId,
api
}: LoadDisplaySettingsParams) {
const settings = (!userId || userId === currentUser?.Id) ? currentSettings : new UserSettings();
const user = (!userId || userId === currentUser?.Id) ? currentUser : await api.getUser(userId);
await settings.setUserInfo(userId, api);
const displaySettings = {
customCss: settings.customCss(),
dashboardTheme: settings.dashboardTheme() || 'auto',
dateTimeLocale: settings.dateTimeLocale() || 'auto',
disableCustomCss: Boolean(settings.disableCustomCss()),
displayMissingEpisodes: user?.Configuration?.DisplayMissingEpisodes ?? false,
enableBlurHash: Boolean(settings.enableBlurhash()),
enableFasterAnimation: Boolean(settings.enableFastFadein()),
enableItemDetailsBanner: Boolean(settings.detailsBanner()),
enableLibraryBackdrops: Boolean(settings.enableBackdrops()),
enableLibraryThemeSongs: Boolean(settings.enableThemeSongs()),
enableLibraryThemeVideos: Boolean(settings.enableThemeVideos()),
enableRewatchingInNextUp: Boolean(settings.enableRewatchingInNextUp()),
episodeImagesInNextUp: Boolean(settings.useEpisodeImagesInNextUpAndResume()),
language: settings.language() || 'auto',
layout: layoutManager.getSavedLayout() || 'auto',
libraryPageSize: settings.libraryPageSize(),
maxDaysForNextUp: settings.maxDaysForNextUp(),
screensaver: settings.screensaver() || 'none',
screensaverInterval: settings.backdropScreensaverInterval(),
theme: settings.theme()
};
return {
displaySettings,
userSettings: settings
};
}
interface SaveDisplaySettingsParams {
api: ApiClient;
newDisplaySettings: DisplaySettingsValues
userSettings: UserSettings;
userId: string;
}
async function saveDisplaySettings({
api,
newDisplaySettings,
userSettings,
userId
}: SaveDisplaySettingsParams) {
const user = await api.getUser(userId);
if (appHost.supports('displaylanguage')) {
userSettings.language(normalizeValue(newDisplaySettings.language));
}
userSettings.customCss(normalizeValue(newDisplaySettings.customCss));
userSettings.dashboardTheme(normalizeValue(newDisplaySettings.dashboardTheme));
userSettings.dateTimeLocale(normalizeValue(newDisplaySettings.dateTimeLocale));
userSettings.disableCustomCss(newDisplaySettings.disableCustomCss);
userSettings.enableBlurhash(newDisplaySettings.enableBlurHash);
userSettings.enableFastFadein(newDisplaySettings.enableFasterAnimation);
userSettings.detailsBanner(newDisplaySettings.enableItemDetailsBanner);
userSettings.enableBackdrops(newDisplaySettings.enableLibraryBackdrops);
userSettings.enableThemeSongs(newDisplaySettings.enableLibraryThemeSongs);
userSettings.enableThemeVideos(newDisplaySettings.enableLibraryThemeVideos);
userSettings.enableRewatchingInNextUp(newDisplaySettings.enableRewatchingInNextUp);
userSettings.useEpisodeImagesInNextUpAndResume(newDisplaySettings.episodeImagesInNextUp);
userSettings.libraryPageSize(newDisplaySettings.libraryPageSize);
userSettings.maxDaysForNextUp(newDisplaySettings.maxDaysForNextUp);
userSettings.screensaver(normalizeValue(newDisplaySettings.screensaver));
userSettings.backdropScreensaverInterval(newDisplaySettings.screensaverInterval);
userSettings.theme(newDisplaySettings.theme);
layoutManager.setLayout(normalizeValue(newDisplaySettings.layout));
const promises = [
themeManager.setTheme(userSettings.theme())
];
if (user.Id && user.Configuration) {
user.Configuration.DisplayMissingEpisodes = newDisplaySettings.displayMissingEpisodes;
promises.push(api.updateUserConfiguration(user.Id, user.Configuration));
}
await Promise.all(promises);
}
function normalizeValue(value: string) {
return /^(auto|none)$/.test(value) ? '' : value;
}

View file

@ -0,0 +1,29 @@
import { useMemo } from 'react';
import { pluginManager } from 'components/pluginManager';
import { Plugin, PluginType } from 'types/plugin';
import globalize from 'scripts/globalize';
export function useScreensavers() {
const screensavers = useMemo<Plugin[]>(() => {
const installedScreensaverPlugins = pluginManager
.ofType(PluginType.Screensaver)
.map((plugin: Plugin) => ({
...plugin,
name: globalize.translate(plugin.name) as string
}));
return [
{
id: 'none',
name: globalize.translate('None') as string,
type: PluginType.Screensaver
},
...installedScreensaverPlugins
];
}, []);
return {
screensavers: screensavers ?? []
};
}

View file

@ -0,0 +1,32 @@
import { useEffect, useMemo, useState } from 'react';
import themeManager from 'scripts/themeManager';
import { Theme } from 'types/webConfig';
export function useServerThemes() {
const [themes, setThemes] = useState<Theme[]>();
useEffect(() => {
async function getServerThemes() {
const loadedThemes = await themeManager.getThemes();
setThemes(loadedThemes ?? []);
}
if (!themes) {
void getServerThemes();
}
// We've intentionally left the dependency array here to ensure that the effect happens only once.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const defaultTheme = useMemo(() => {
if (!themes) return null;
return themes.find((theme) => theme.default);
}, [themes]);
return {
themes: themes ?? [],
defaultTheme
};
}

View file

@ -0,0 +1,96 @@
import Button from '@mui/material/Button';
import { SelectChangeEvent } from '@mui/material/Select';
import Stack from '@mui/material/Stack';
import React, { useCallback } from 'react';
import Page from 'components/Page';
import globalize from 'scripts/globalize';
import theme from 'themes/theme';
import { DisplayPreferences } from './DisplayPreferences';
import { ItemDetailPreferences } from './ItemDetailPreferences';
import { LibraryPreferences } from './LibraryPreferences';
import { LocalizationPreferences } from './LocalizationPreferences';
import { NextUpPreferences } from './NextUpPreferences';
import { useDisplaySettingForm } from './hooks/useDisplaySettingForm';
import { DisplaySettingsValues } from './types';
import LoadingComponent from 'components/loading/LoadingComponent';
export default function UserDisplayPreferences() {
const {
loading,
submitChanges,
updateField,
values
} = useDisplaySettingForm();
const handleSubmitForm = useCallback((e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
void submitChanges();
}, [submitChanges]);
const handleFieldChange = useCallback((e: SelectChangeEvent | React.SyntheticEvent) => {
const target = e.target as HTMLInputElement;
const fieldName = target.name as keyof DisplaySettingsValues;
const fieldValue = target.type === 'checkbox' ? target.checked : target.value;
if (values?.[fieldName] !== fieldValue) {
updateField({
name: fieldName,
value: fieldValue
});
}
}, [updateField, values]);
if (loading || !values) {
return <LoadingComponent />;
}
return (
<Page
className='libraryPage userPreferencesPage noSecondaryNavPage'
id='displayPreferencesPage'
title={globalize.translate('Display')}
>
<div className='settingsContainer padded-left padded-right padded-bottom-page'>
<form
onSubmit={handleSubmitForm}
style={{ margin: 'auto' }}
>
<Stack spacing={4}>
<LocalizationPreferences
onChange={handleFieldChange}
values={values}
/>
<DisplayPreferences
onChange={handleFieldChange}
values={values}
/>
<LibraryPreferences
onChange={handleFieldChange}
values={values}
/>
<NextUpPreferences
onChange={handleFieldChange}
values={values}
/>
<ItemDetailPreferences
onChange={handleFieldChange}
values={values}
/>
<Button
type='submit'
sx={{
color: theme.palette.text.primary,
fontSize: theme.typography.htmlFontSize,
fontWeight: theme.typography.fontWeightBold
}}
>
{globalize.translate('Save')}
</Button>
</Stack>
</form>
</div>
</Page>
);
}

View file

@ -0,0 +1,22 @@
export interface DisplaySettingsValues {
customCss: string;
dashboardTheme: string;
dateTimeLocale: string;
disableCustomCss: boolean;
displayMissingEpisodes: boolean;
enableBlurHash: boolean;
enableFasterAnimation: boolean;
enableItemDetailsBanner: boolean;
enableLibraryBackdrops: boolean;
enableLibraryThemeSongs: boolean;
enableLibraryThemeVideos: boolean;
enableRewatchingInNextUp: boolean;
episodeImagesInNextUp: boolean;
language: string;
layout: string;
libraryPageSize: number;
maxDaysForNextUp: number;
screensaver: string;
screensaverInterval: number;
theme: string;
}

View file

@ -19,6 +19,12 @@ export const LEGACY_USER_ROUTES: LegacyRoute[] = [
controller: 'livetv/livetvsuggested', controller: 'livetv/livetvsuggested',
view: 'livetv.html' view: 'livetv.html'
} }
}, {
path: 'lyrics',
pageProps: {
controller: 'lyrics',
view: 'lyrics.html'
}
}, { }, {
path: 'music.html', path: 'music.html',
pageProps: { pageProps: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Before After
Before After

View file

@ -31,6 +31,7 @@ const ResponsiveDrawer: FC<ResponsiveDrawerProps> = ({
flexShrink: 0, flexShrink: 0,
'& .MuiDrawer-paper': { '& .MuiDrawer-paper': {
width: DRAWER_WIDTH, width: DRAWER_WIDTH,
paddingBottom: '4.2rem', // Padding for now playing bar
boxSizing: 'border-box' boxSizing: 'border-box'
} }
}} }}

View file

@ -104,6 +104,18 @@ class ServerConnections extends ConnectionManager {
return apiClient; return apiClient;
} }
/**
* Gets the ApiClient that is currently connected or throws if not defined.
* @async
* @returns {Promise<ApiClient>} The current ApiClient instance.
*/
async getCurrentApiClientAsync() {
const apiClient = this.currentApiClient();
if (!apiClient) throw new Error('[ServerConnection] No current ApiClient instance');
return apiClient;
}
onLocalUserSignedIn(user) { onLocalUserSignedIn(user) {
const apiClient = this.getApiClient(user.ServerId); const apiClient = this.getApiClient(user.ServerId);
this.setLocalApiClient(apiClient); this.setLocalApiClient(apiClient);

View file

@ -45,18 +45,27 @@ function getDeviceProfile(item) {
const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth; const maxTranscodingVideoWidth = maxVideoWidth < 0 ? appHost.screen()?.maxAllowedWidth : maxVideoWidth;
if (maxTranscodingVideoWidth) { if (maxTranscodingVideoWidth) {
const conditionWidth = {
Condition: 'LessThanEqual',
Property: 'Width',
Value: maxTranscodingVideoWidth.toString(),
IsRequired: false
};
if (appSettings.limitSupportedVideoResolution()) {
profile.CodecProfiles.push({
Type: 'Video',
Conditions: [conditionWidth]
});
}
profile.TranscodingProfiles.forEach((transcodingProfile) => { profile.TranscodingProfiles.forEach((transcodingProfile) => {
if (transcodingProfile.Type === 'Video') { if (transcodingProfile.Type === 'Video') {
transcodingProfile.Conditions = (transcodingProfile.Conditions || []).filter((condition) => { transcodingProfile.Conditions = (transcodingProfile.Conditions || []).filter((condition) => {
return condition.Property !== 'Width'; return condition.Property !== 'Width';
}); });
transcodingProfile.Conditions.push({ transcodingProfile.Conditions.push(conditionWidth);
Condition: 'LessThanEqual',
Property: 'Width',
Value: maxTranscodingVideoWidth.toString(),
IsRequired: false
});
} }
}); });
} }

View file

@ -1,33 +0,0 @@
import React, { type FunctionComponent } from 'react';
import globalize from '../../../../scripts/globalize';
type IProps = {
title?: string;
className?: string;
href?: string;
};
const createLinkElement = ({ className, title, href }: IProps) => ({
__html: `<a
is="emby-linkbutton"
rel="noopener noreferrer"
class="${className}"
href="${href}"
>
${title}
</a>`
});
const LinkTrickplayAcceleration: FunctionComponent<IProps> = ({ className, title, href }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createLinkElement({
className,
title: globalize.translate(title),
href
})}
/>
);
};
export default LinkTrickplayAcceleration;

View file

@ -2,10 +2,11 @@ import React, { FunctionComponent } from 'react';
import IconButtonElement from '../../../elements/IconButtonElement'; import IconButtonElement from '../../../elements/IconButtonElement';
type IProps = { type IProps = {
tag?: string; tag?: string,
tagType?: string;
}; };
const BlockedTagList: FunctionComponent<IProps> = ({ tag }: IProps) => { const TagList: FunctionComponent<IProps> = ({ tag, tagType }: IProps) => {
return ( return (
<div className='paperList'> <div className='paperList'>
<div className='listItem'> <div className='listItem'>
@ -16,7 +17,7 @@ const BlockedTagList: FunctionComponent<IProps> = ({ tag }: IProps) => {
</div> </div>
<IconButtonElement <IconButtonElement
is='paper-icon-button-light' is='paper-icon-button-light'
className='blockedTag btnDeleteTag listItemButton' className={`${tagType} btnDeleteTag listItemButton`}
title='Delete' title='Delete'
icon='delete' icon='delete'
dataTag={tag} dataTag={tag}
@ -26,4 +27,4 @@ const BlockedTagList: FunctionComponent<IProps> = ({ tag }: IProps) => {
); );
}; };
export default BlockedTagList; export default TagList;

View file

@ -1,6 +1,7 @@
import appSettings from '../scripts/settings/appSettings' ; import appSettings from '../scripts/settings/appSettings' ;
import browser from '../scripts/browser'; import browser from '../scripts/browser';
import Events from '../utils/events.ts'; import Events from '../utils/events.ts';
import { MediaError } from 'types/mediaError';
export function getSavedVolume() { export function getSavedVolume() {
return appSettings.get('volume') || 1; return appSettings.get('volume') || 1;
@ -87,7 +88,7 @@ export function handleHlsJsMediaError(instance, reject) {
if (reject) { if (reject) {
reject(); reject();
} else { } else {
onErrorInternal(instance, 'mediadecodeerror'); onErrorInternal(instance, MediaError.FATAL_HLS_ERROR);
} }
} }
} }
@ -98,11 +99,7 @@ export function onErrorInternal(instance, type) {
instance.destroyCustomTrack(instance._mediaElement); instance.destroyCustomTrack(instance._mediaElement);
} }
Events.trigger(instance, 'error', [ Events.trigger(instance, 'error', [{ type }]);
{
type: type
}
]);
} }
export function isValidDuration(duration) { export function isValidDuration(duration) {
@ -193,7 +190,7 @@ export function playWithPromise(elem, onErrorFn) {
// swallow this error because the user can still click the play button on the video element // swallow this error because the user can still click the play button on the video element
return Promise.resolve(); return Promise.resolve();
} }
return Promise.reject(); return Promise.reject(e);
}) })
.then(() => { .then(() => {
onSuccessfulPlay(elem, onErrorFn); onSuccessfulPlay(elem, onErrorFn);
@ -269,10 +266,10 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
hls.destroy(); hls.destroy();
if (reject) { if (reject) {
reject('servererror'); reject(MediaError.SERVER_ERROR);
reject = null; reject = null;
} else { } else {
onErrorInternal(instance, 'servererror'); onErrorInternal(instance, MediaError.SERVER_ERROR);
} }
return; return;
@ -291,10 +288,10 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
hls.destroy(); hls.destroy();
if (reject) { if (reject) {
reject('network'); reject(MediaError.NETWORK_ERROR);
reject = null; reject = null;
} else { } else {
onErrorInternal(instance, 'network'); onErrorInternal(instance, MediaError.NETWORK_ERROR);
} }
} else { } else {
console.debug('fatal network error encountered, try to recover'); console.debug('fatal network error encountered, try to recover');
@ -318,7 +315,7 @@ export function bindEventsToHlsPlayer(instance, hls, elem, onErrorFn, resolve, r
reject(); reject();
reject = null; reject = null;
} else { } else {
onErrorInternal(instance, 'mediadecodeerror'); onErrorInternal(instance, MediaError.FATAL_HLS_ERROR);
} }
break; break;
} }

View file

@ -10,6 +10,24 @@ import { playbackManager } from './playback/playbackmanager';
import ServerConnections from './ServerConnections'; import ServerConnections from './ServerConnections';
import toast from './toast/toast'; import toast from './toast/toast';
import * as userSettings from '../scripts/settings/userSettings'; import * as userSettings from '../scripts/settings/userSettings';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
function getDeleteLabel(type) {
switch (type) {
case BaseItemKind.Series:
return globalize.translate('DeleteSeries');
case BaseItemKind.Episode:
return globalize.translate('DeleteEpisode');
case BaseItemKind.Playlist:
case BaseItemKind.BoxSet:
return globalize.translate('Delete');
default:
return globalize.translate('DeleteMedia');
}
}
export function getCommands(options) { export function getCommands(options) {
const item = options.item; const item = options.item;
@ -160,17 +178,17 @@ export function getCommands(options) {
} }
if (item.CanDelete && options.deleteItem !== false) { if (item.CanDelete && options.deleteItem !== false) {
if (item.Type === 'Playlist' || item.Type === 'BoxSet') {
commands.push({ commands.push({
name: globalize.translate('Delete'), name: getDeleteLabel(item.Type),
id: 'delete', id: 'delete',
icon: 'delete' icon: 'delete'
}); });
} else {
if (item.Type === 'Audio' && item.HasLyrics && window.location.href.includes(item.Id)) {
commands.push({ commands.push({
name: globalize.translate('DeleteMedia'), name: globalize.translate('DeleteLyrics'),
id: 'delete', id: 'deleteLyrics',
icon: 'delete' icon: 'delete_sweep'
}); });
} }
} }
@ -303,6 +321,14 @@ export function getCommands(options) {
}); });
} }
if (item.HasLyrics) {
commands.push({
name: globalize.translate('ViewLyrics'),
id: 'lyrics',
icon: 'lyrics'
});
}
return commands; return commands;
} }
@ -485,6 +511,9 @@ function executeCommand(item, id, options) {
case 'delete': case 'delete':
deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id)); deleteItem(apiClient, item).then(getResolveFunction(resolve, id, true, true), getResolveFunction(resolve, id));
break; break;
case 'deleteLyrics':
deleteLyrics(apiClient, item).then(getResolveFunction(resolve, id, true), getResolveFunction(resolve, id));
break;
case 'share': case 'share':
navigator.share({ navigator.share({
title: item.Name, title: item.Name,
@ -500,6 +529,15 @@ function executeCommand(item, id, options) {
appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId); appRouter.showItem(item.AlbumArtists[0].Id, item.ServerId);
getResolveFunction(resolve, id)(); getResolveFunction(resolve, id)();
break; break;
case 'lyrics': {
if (options.isMobile) {
appRouter.show('lyrics');
} else {
appRouter.showItem(item.Id, item.ServerId);
}
getResolveFunction(resolve, id)();
break;
}
case 'playallfromhere': case 'playallfromhere':
getResolveFunction(resolve, id)(); getResolveFunction(resolve, id)();
break; break;
@ -626,6 +664,12 @@ function deleteItem(apiClient, item) {
}); });
} }
function deleteLyrics(apiClient, item) {
return import('../scripts/deleteHelper').then((deleteHelper) => {
return deleteHelper.deleteLyrics(item);
});
}
function refresh(apiClient, item) { function refresh(apiClient, item) {
import('./refreshdialog/refreshdialog').then(({ default: RefreshDialog }) => { import('./refreshdialog/refreshdialog').then(({ default: RefreshDialog }) => {
new RefreshDialog({ new RefreshDialog({

View file

@ -84,6 +84,7 @@ function getMediaSourceHtml(user, item, version) {
case 'Data': case 'Data':
case 'Subtitle': case 'Subtitle':
case 'Video': case 'Video':
case 'Lyric':
translateString = stream.Type; translateString = stream.Type;
break; break;
case 'EmbeddedImage': case 'EmbeddedImage':
@ -145,10 +146,10 @@ function getMediaSourceHtml(user, item, version) {
if (stream.BitDepth) { if (stream.BitDepth) {
attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`)); attributes.push(createAttribute(globalize.translate('MediaInfoBitDepth'), `${stream.BitDepth} bit`));
} }
if (stream.VideoRange) { if (stream.VideoRange && stream.Type === 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange)); attributes.push(createAttribute(globalize.translate('MediaInfoVideoRange'), stream.VideoRange));
} }
if (stream.VideoRangeType) { if (stream.VideoRangeType && stream.Type === 'Video') {
attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType)); attributes.push(createAttribute(globalize.translate('MediaInfoVideoRangeType'), stream.VideoRangeType));
} }
if (stream.VideoDoViTitle) { if (stream.VideoDoViTitle) {

View file

@ -418,8 +418,6 @@ export function setContentType(parent, contentType) {
} }
} }
parent.querySelector('.chkUseReplayGainTagsContainer').classList.toggle('hide', contentType !== 'music');
parent.querySelector('.chkEnableLUFSScanContainer').classList.toggle('hide', contentType !== 'music'); parent.querySelector('.chkEnableLUFSScanContainer').classList.toggle('hide', contentType !== 'music');
if (contentType === 'tvshows') { if (contentType === 'tvshows') {
@ -515,13 +513,13 @@ function setImageOptionsIntoOptions(options) {
export function getLibraryOptions(parent) { export function getLibraryOptions(parent) {
const options = { const options = {
Enabled: parent.querySelector('.chkEnabled').checked,
EnableArchiveMediaFiles: false, EnableArchiveMediaFiles: false,
EnablePhotos: parent.querySelector('.chkEnablePhotos').checked, EnablePhotos: parent.querySelector('.chkEnablePhotos').checked,
EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked, EnableRealtimeMonitor: parent.querySelector('.chkEnableRealtimeMonitor').checked,
EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked, EnableLUFSScan: parent.querySelector('.chkEnableLUFSScan').checked,
ExtractTrickplayImagesDuringLibraryScan: parent.querySelector('.chkExtractTrickplayDuringLibraryScan').checked, ExtractTrickplayImagesDuringLibraryScan: parent.querySelector('.chkExtractTrickplayDuringLibraryScan').checked,
EnableTrickplayImageExtraction: parent.querySelector('.chkExtractTrickplayImages').checked, EnableTrickplayImageExtraction: parent.querySelector('.chkExtractTrickplayImages').checked,
UseReplayGainTags: parent.querySelector('.chkUseReplayGainTags').checked,
ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked, ExtractChapterImagesDuringLibraryScan: parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked,
EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked, EnableChapterImageExtraction: parent.querySelector('.chkExtractChapterImages').checked,
EnableInternetProviders: true, EnableInternetProviders: true,
@ -581,12 +579,12 @@ export function setLibraryOptions(parent, options) {
parent.querySelector('#selectCountry').value = options.MetadataCountryCode || ''; parent.querySelector('#selectCountry').value = options.MetadataCountryCode || '';
parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0'; parent.querySelector('#selectAutoRefreshInterval').value = options.AutomaticRefreshIntervalDays || '0';
parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials'; parent.querySelector('#txtSeasonZeroName').value = options.SeasonZeroDisplayName || 'Specials';
parent.querySelector('.chkEnabled').checked = options.Enabled;
parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos; parent.querySelector('.chkEnablePhotos').checked = options.EnablePhotos;
parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor; parent.querySelector('.chkEnableRealtimeMonitor').checked = options.EnableRealtimeMonitor;
parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan; parent.querySelector('.chkEnableLUFSScan').checked = options.EnableLUFSScan;
parent.querySelector('.chkExtractTrickplayDuringLibraryScan').checked = options.ExtractTrickplayImagesDuringLibraryScan; parent.querySelector('.chkExtractTrickplayDuringLibraryScan').checked = options.ExtractTrickplayImagesDuringLibraryScan;
parent.querySelector('.chkExtractTrickplayImages').checked = options.EnableTrickplayImageExtraction; parent.querySelector('.chkExtractTrickplayImages').checked = options.EnableTrickplayImageExtraction;
parent.querySelector('.chkUseReplayGainTags').checked = options.UseReplayGainTags;
parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan; parent.querySelector('.chkExtractChaptersDuringLibraryScan').checked = options.ExtractChapterImagesDuringLibraryScan;
parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction; parent.querySelector('.chkExtractChapterImages').checked = options.EnableChapterImageExtraction;
parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata; parent.querySelector('#chkSaveLocal').checked = options.SaveLocalMetadata;

View file

@ -1,4 +1,12 @@
<h2>${HeaderLibrarySettings}</h2> <h2>${HeaderLibrarySettings}</h2>
<div class="checkboxContainer checkboxContainer-withDescription chkEnabledContainer">
<label>
<input type="checkbox" is="emby-checkbox" class="chkEnabled" checked />
<span>${EnableLibrary}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${EnableLibraryHelp}</div>
</div>
<div class="selectContainer fldMetadataLanguage hide"> <div class="selectContainer fldMetadataLanguage hide">
<select is="emby-select" id="selectLanguage" label="${LabelMetadataDownloadLanguage}"></select> <select is="emby-select" id="selectLanguage" label="${LabelMetadataDownloadLanguage}"></select>
</div> </div>
@ -55,14 +63,6 @@
<div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div> <div class="fieldDescription checkboxFieldDescription">${LabelEnableRealtimeMonitorHelp}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription chkUseReplayGainTagsContainer advanced">
<label>
<input type="checkbox" is="emby-checkbox" class="chkUseReplayGainTags" checked />
<span>${LabelUseReplayGainTags}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LabelUseReplayGainTagsHelp}</div>
</div>
<div class="checkboxContainer checkboxContainer-withDescription chkEnableLUFSScanContainer advanced"> <div class="checkboxContainer checkboxContainer-withDescription chkEnableLUFSScanContainer advanced">
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkEnableLUFSScan" checked /> <input type="checkbox" is="emby-checkbox" class="chkEnableLUFSScan" checked />

View file

@ -22,6 +22,7 @@ import ServerConnections from '../ServerConnections';
import toast from '../toast/toast'; import toast from '../toast/toast';
import { appRouter } from '../router/appRouter'; import { appRouter } from '../router/appRouter';
import template from './metadataEditor.template.html'; import template from './metadataEditor.template.html';
import { SeriesStatus } from '@jellyfin/sdk/lib/generated-client';
let currentContext; let currentContext;
let metadataEditorInfo; let metadataEditorInfo;
@ -886,10 +887,10 @@ function populateRatings(allParentalRatings, select, currentValue) {
function populateStatus(select) { function populateStatus(select) {
let html = ''; let html = '';
html += '<option value=""></option>';
html += "<option value=''></option>"; html += `<option value="${SeriesStatus.Continuing}">${escapeHtml(globalize.translate('Continuing'))}</option>`;
html += "<option value='Continuing'>" + globalize.translate('Continuing') + '</option>'; html += `<option value="${SeriesStatus.Ended}">${escapeHtml(globalize.translate('Ended'))}</option>`;
html += "<option value='Ended'>" + globalize.translate('Ended') + '</option>'; html += `<option value="${SeriesStatus.Unreleased}">${escapeHtml(globalize.translate('Unreleased'))}</option>`;
select.innerHTML = html; select.innerHTML = html;
} }

View file

@ -7,6 +7,7 @@ import { playbackManager } from '../playback/playbackmanager';
import nowPlayingHelper from '../playback/nowplayinghelper'; import nowPlayingHelper from '../playback/nowplayinghelper';
import { appHost } from '../apphost'; import { appHost } from '../apphost';
import dom from '../../scripts/dom'; import dom from '../../scripts/dom';
import globalize from 'scripts/globalize';
import itemContextMenu from '../itemContextMenu'; import itemContextMenu from '../itemContextMenu';
import '../../elements/emby-button/paper-icon-button-light'; import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-ratingbutton/emby-ratingbutton'; import '../../elements/emby-ratingbutton/emby-ratingbutton';
@ -33,6 +34,7 @@ let positionSlider;
let toggleAirPlayButton; let toggleAirPlayButton;
let toggleRepeatButton; let toggleRepeatButton;
let toggleRepeatButtonIcon; let toggleRepeatButtonIcon;
let lyricButton;
let lastUpdateTime = 0; let lastUpdateTime = 0;
let lastPlayerState = {}; let lastPlayerState = {};
@ -41,6 +43,9 @@ let currentRuntimeTicks = 0;
let isVisibilityAllowed = true; let isVisibilityAllowed = true;
let lyricPageActive = false;
let isAudio = false;
function getNowPlayingBarHtml() { function getNowPlayingBarHtml() {
let html = ''; let html = '';
@ -59,13 +64,13 @@ function getNowPlayingBarHtml() {
// The onclicks are needed due to the return false above // The onclicks are needed due to the return false above
html += '<div class="nowPlayingBarCenter" dir="ltr">'; html += '<div class="nowPlayingBarCenter" dir="ltr">';
html += '<button is="paper-icon-button-light" class="previousTrackButton mediaButton"><span class="material-icons skip_previous" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="previousTrackButton mediaButton" title="${globalize.translate('ButtonPreviousTrack')}"><span class="material-icons skip_previous" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="playPauseButton mediaButton" title="${globalize.translate('ButtonPause')}"><span class="material-icons pause" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="stopButton mediaButton"><span class="material-icons stop" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="stopButton mediaButton" title="${globalize.translate('ButtonStop')}"><span class="material-icons stop" aria-hidden="true"></span></button>`;
if (!layoutManager.mobile) { if (!layoutManager.mobile) {
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="nextTrackButton mediaButton" title="${globalize.translate('ButtonNextTrack')}"><span class="material-icons skip_next" aria-hidden="true"></span></button>`;
} }
html += '<div class="nowPlayingBarCurrentTime"></div>'; html += '<div class="nowPlayingBarCurrentTime"></div>';
@ -73,25 +78,27 @@ function getNowPlayingBarHtml() {
html += '<div class="nowPlayingBarRight">'; html += '<div class="nowPlayingBarRight">';
html += '<button is="paper-icon-button-light" class="muteButton mediaButton"><span class="material-icons volume_up" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="muteButton mediaButton" title="${globalize.translate('Mute')}"><span class="material-icons volume_up" aria-hidden="true"></span></button>`;
html += '<div class="sliderContainer nowPlayingBarVolumeSliderContainer hide" style="width:9em;vertical-align:middle;display:inline-flex;">'; html += '<div class="sliderContainer nowPlayingBarVolumeSliderContainer hide" style="width:9em;vertical-align:middle;display:inline-flex;">';
html += '<input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarVolumeSlider"/>'; html += '<input type="range" is="emby-slider" pin step="1" min="0" max="100" value="0" class="slider-medium-thumb nowPlayingBarVolumeSlider"/>';
html += '</div>'; html += '</div>';
html += '<button is="paper-icon-button-light" class="btnAirPlay mediaButton"><span class="material-icons airplay" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="btnAirPlay mediaButton" title="${globalize.translate('AirPlay')}"><span class="material-icons airplay" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton"><span class="material-icons repeat" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="openLyricsButton mediaButton" title="${globalize.translate('Lyrics')}"><span class="material-icons lyrics" style="top:0.1em" aria-hidden="true"></span></button>`;
html += '<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton"><span class="material-icons shuffle" aria-hidden="true"></span></button>';
html += `<button is="paper-icon-button-light" class="toggleRepeatButton mediaButton" title="${globalize.translate('Repeat')}"><span class="material-icons repeat" aria-hidden="true"></span></button>`;
html += `<button is="paper-icon-button-light" class="btnShuffleQueue mediaButton" title="${globalize.translate('Shuffle')}"><span class="material-icons shuffle" aria-hidden="true"></span></button>`;
html += '<div class="nowPlayingBarUserDataButtons">'; html += '<div class="nowPlayingBarUserDataButtons">';
html += '</div>'; html += '</div>';
html += '<button is="paper-icon-button-light" class="playPauseButton mediaButton"><span class="material-icons pause" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="playPauseButton mediaButton" title="${globalize.translate('ButtonPause')}"><span class="material-icons pause" aria-hidden="true"></span></button>`;
if (layoutManager.mobile) { if (layoutManager.mobile) {
html += '<button is="paper-icon-button-light" class="nextTrackButton mediaButton"><span class="material-icons skip_next" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="nextTrackButton mediaButton" title="${globalize.translate('ButtonNextTrack')}"><span class="material-icons skip_next" aria-hidden="true"></span></button>`;
} else { } else {
html += '<button is="paper-icon-button-light" class="btnToggleContextMenu mediaButton"><span class="material-icons more_vert" aria-hidden="true"></span></button>'; html += `<button is="paper-icon-button-light" class="btnToggleContextMenu mediaButton" title="${globalize.translate('ButtonMore')}"><span class="material-icons more_vert" aria-hidden="true"></span></button>`;
} }
html += '</div>'; html += '</div>';
@ -145,6 +152,7 @@ function bindEvents(elem) {
toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider'); volumeSlider = elem.querySelector('.nowPlayingBarVolumeSlider');
volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer'); volumeSliderContainer = elem.querySelector('.nowPlayingBarVolumeSliderContainer');
lyricButton = nowPlayingBarElement.querySelector('.openLyricsButton');
muteButton.addEventListener('click', function () { muteButton.addEventListener('click', function () {
if (currentPlayer) { if (currentPlayer) {
@ -211,6 +219,14 @@ function bindEvents(elem) {
} }
}); });
lyricButton.addEventListener('click', function() {
if (lyricPageActive) {
appRouter.back();
} else {
appRouter.show('lyrics');
}
});
toggleRepeatButton = elem.querySelector('.toggleRepeatButton'); toggleRepeatButton = elem.querySelector('.toggleRepeatButton');
toggleRepeatButton.addEventListener('click', function () { toggleRepeatButton.addEventListener('click', function () {
switch (playbackManager.getRepeatMode()) { switch (playbackManager.getRepeatMode()) {
@ -317,6 +333,7 @@ function updatePlayPauseState(isPaused) {
const icon = button.querySelector('.material-icons'); const icon = button.querySelector('.material-icons');
icon.classList.remove('play_arrow', 'pause'); icon.classList.remove('play_arrow', 'pause');
icon.classList.add(isPaused ? 'play_arrow' : 'pause'); icon.classList.add(isPaused ? 'play_arrow' : 'pause');
button.title = globalize.translate(isPaused ? 'Play' : 'ButtonPause');
}); });
} }
} }
@ -361,6 +378,7 @@ function updatePlayerStateInternal(event, state, player) {
updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player)); updateTimeDisplay(playState.PositionTicks, nowPlayingItem.RunTimeTicks, playbackManager.getBufferedRanges(player));
updateNowPlayingInfo(state); updateNowPlayingInfo(state);
updateLyricButton();
} }
function updateRepeatModeDisplay(repeatMode) { function updateRepeatModeDisplay(repeatMode) {
@ -424,6 +442,7 @@ function updatePlayerVolumeState(isMuted, volumeLevel) {
const muteButtonIcon = muteButton.querySelector('.material-icons'); const muteButtonIcon = muteButton.querySelector('.material-icons');
muteButtonIcon.classList.remove('volume_off', 'volume_up'); muteButtonIcon.classList.remove('volume_off', 'volume_up');
muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up'); muteButtonIcon.classList.add(isMuted ? 'volume_off' : 'volume_up');
muteButton.title = globalize.translate(isMuted ? 'Unmute' : 'Mute');
if (supportedCommands.indexOf('SetVolume') === -1) { if (supportedCommands.indexOf('SetVolume') === -1) {
showVolumeSlider = false; showVolumeSlider = false;
@ -450,6 +469,22 @@ function updatePlayerVolumeState(isMuted, volumeLevel) {
} }
} }
function updateLyricButton() {
if (!isEnabled) {
return;
}
isAudio ? showButton(lyricButton) : hideButton(lyricButton);
setLyricButtonActiveStatus();
}
function setLyricButtonActiveStatus() {
if (!isEnabled) {
return;
}
lyricButton.classList.toggle('buttonActive', lyricPageActive);
}
function seriesImageUrl(item, options) { function seriesImageUrl(item, options) {
if (!item) { if (!item) {
throw new Error('item cannot be null!'); throw new Error('item cannot be null!');
@ -582,7 +617,7 @@ function updateNowPlayingInfo(state) {
}); });
}); });
} }
nowPlayingUserData.innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton mediaButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>'; nowPlayingUserData.innerHTML = '<button is="emby-ratingbutton" type="button" class="mediaButton paper-icon-button-light" data-id="' + item.Id + '" data-serverid="' + item.ServerId + '" data-itemtype="' + item.Type + '" data-likes="' + likes + '" data-isfavorite="' + (userData.IsFavorite) + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
}); });
} else { } else {
nowPlayingUserData.innerHTML = ''; nowPlayingUserData.innerHTML = '';
@ -592,6 +627,9 @@ function updateNowPlayingInfo(state) {
function onPlaybackStart(e, state) { function onPlaybackStart(e, state) {
console.debug('nowplaying event: ' + e.type); console.debug('nowplaying event: ' + e.type);
const player = this; const player = this;
isAudio = state.NowPlayingItem.Type === 'Audio';
onStateChanged.call(player, e, state); onStateChanged.call(player, e, state);
} }
@ -695,6 +733,7 @@ function onStateChanged(event, state) {
} }
getNowPlayingBar(); getNowPlayingBar();
updateLyricButton();
updatePlayerStateInternal(event, state, player); updatePlayerStateInternal(event, state, player);
} }
@ -751,6 +790,7 @@ function refreshFromPlayer(player, type) {
} }
function bindToPlayer(player) { function bindToPlayer(player) {
lyricPageActive = appRouter.currentRouteInfo.path.toLowerCase() === '/lyrics';
if (player === currentPlayer) { if (player === currentPlayer) {
return; return;
} }
@ -783,6 +823,8 @@ Events.on(playbackManager, 'playerchange', function () {
bindToPlayer(playbackManager.getCurrentPlayer()); bindToPlayer(playbackManager.getCurrentPlayer());
document.addEventListener('viewbeforeshow', function (e) { document.addEventListener('viewbeforeshow', function (e) {
lyricPageActive = appRouter.currentRouteInfo.path.toLowerCase() === '/lyrics';
setLyricButtonActiveStatus();
if (!e.detail.options.enableMediaControl) { if (!e.detail.options.enableMediaControl) {
if (isVisibilityAllowed) { if (isVisibilityAllowed) {
isVisibilityAllowed = false; isVisibilityAllowed = false;

View file

@ -1,3 +1,7 @@
import { PlaybackErrorCode } from '@jellyfin/sdk/lib/generated-client/models/playback-error-code.js';
import merge from 'lodash-es/merge';
import Screenfull from 'screenfull';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import datetime from '../../scripts/datetime'; import datetime from '../../scripts/datetime';
import appSettings from '../../scripts/settings/appSettings'; import appSettings from '../../scripts/settings/appSettings';
@ -8,14 +12,15 @@ import * as userSettings from '../../scripts/settings/userSettings';
import globalize from '../../scripts/globalize'; import globalize from '../../scripts/globalize';
import loading from '../loading/loading'; import loading from '../loading/loading';
import { appHost } from '../apphost'; import { appHost } from '../apphost';
import Screenfull from 'screenfull';
import ServerConnections from '../ServerConnections'; import ServerConnections from '../ServerConnections';
import alert from '../alert'; import alert from '../alert';
import { PluginType } from '../../types/plugin.ts'; import { PluginType } from '../../types/plugin.ts';
import { includesAny } from '../../utils/container.ts'; import { includesAny } from '../../utils/container.ts';
import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts';
import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage'; import { getItemBackdropImageUrl } from '../../utils/jellyfin-apiclient/backdropImage';
import merge from 'lodash-es/merge';
import { MediaError } from 'types/mediaError';
import { getMediaError } from 'utils/mediaError';
const UNLIMITED_ITEMS = -1; const UNLIMITED_ITEMS = -1;
@ -588,9 +593,18 @@ function supportsDirectPlay(apiClient, item, mediaSource) {
return Promise.resolve(false); return Promise.resolve(false);
} }
/**
* @param {PlaybackManager} instance
* @param {import('@jellyfin/sdk/lib/generated-client/index.js').PlaybackInfoResponse} result
* @returns {boolean}
*/
function validatePlaybackInfoResult(instance, result) { function validatePlaybackInfoResult(instance, result) {
if (result.ErrorCode) { if (result.ErrorCode) {
showPlaybackInfoErrorMessage(instance, 'PlaybackError' + result.ErrorCode); // NOTE: To avoid needing to retranslate the "NoCompatibleStream" message,
// we need to keep the key in the same format.
const errMessage = result.ErrorCode === PlaybackErrorCode.NoCompatibleStream ?
'PlaybackErrorNoCompatibleStream' : `PlaybackError.${result.ErrorCode}`;
showPlaybackInfoErrorMessage(instance, errMessage);
return false; return false;
} }
@ -1720,7 +1734,8 @@ class PlaybackManager {
streamInfo.resetSubtitleOffset = false; streamInfo.resetSubtitleOffset = false;
if (!streamInfo.url) { if (!streamInfo.url) {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); cancelPlayback();
showPlaybackInfoErrorMessage(self, `PlaybackError.${MediaError.NO_MEDIA_ERROR}`);
return; return;
} }
@ -1768,8 +1783,8 @@ class PlaybackManager {
playerData.isChangingStream = false; playerData.isChangingStream = false;
onPlaybackError.call(player, e, { onPlaybackError.call(player, e, {
type: 'mediadecodeerror', type: getMediaError(e),
streamInfo: streamInfo streamInfo
}); });
}); });
} }
@ -1853,48 +1868,37 @@ class PlaybackManager {
}, queryOptions)); }, queryOptions));
} else if (firstItem.Type === 'Series' || firstItem.Type === 'Season') { } else if (firstItem.Type === 'Series' || firstItem.Type === 'Season') {
const apiClient = ServerConnections.getApiClient(firstItem.ServerId); const apiClient = ServerConnections.getApiClient(firstItem.ServerId);
const isSeason = firstItem.Type === 'Season';
promise = apiClient.getEpisodes(firstItem.SeriesId || firstItem.Id, { promise = apiClient.getEpisodes(firstItem.SeriesId || firstItem.Id, {
IsVirtualUnaired: false, IsVirtualUnaired: false,
IsMissing: false, IsMissing: false,
SeasonId: isSeason ? firstItem.Id : undefined,
SortBy: options.shuffle ? 'Random' : undefined,
UserId: apiClient.getCurrentUserId(), UserId: apiClient.getCurrentUserId(),
Fields: ['Chapters', 'Trickplay'] Fields: ['Chapters', 'Trickplay']
}).then(function (episodesResult) { }).then(function (episodesResult) {
const originalResults = episodesResult.Items; const originalResults = episodesResult.Items;
const isSeries = firstItem.Type === 'Series';
let foundItem = false; let foundItem = false;
if (!options.shuffle) {
episodesResult.Items = episodesResult.Items.filter(function (e) { episodesResult.Items = episodesResult.Items.filter(function (e) {
if (foundItem) { if (foundItem) {
return true; return true;
} }
if (!e.UserData.Played && (isSeries || e.SeasonId === firstItem.Id)) { if (!e.UserData.Played) {
foundItem = true; foundItem = true;
return true; return true;
} }
return false; return false;
}); });
}
if (episodesResult.Items.length === 0) { if (episodesResult.Items.length === 0) {
if (isSeries) {
episodesResult.Items = originalResults; episodesResult.Items = originalResults;
} else {
episodesResult.Items = originalResults.filter(function (e) {
if (foundItem) {
return true;
}
if (e.SeasonId === firstItem.Id) {
foundItem = true;
return true;
}
return false;
});
}
} }
episodesResult.TotalRecordCount = episodesResult.Items.length; episodesResult.TotalRecordCount = episodesResult.Items.length;
@ -2153,7 +2157,7 @@ class PlaybackManager {
const getAdditionalParts = async (items) => { const getAdditionalParts = async (items) => {
const getOneAdditionalPart = async function (item) { const getOneAdditionalPart = async function (item) {
let retVal = [item]; let retVal = [item];
if (item.Type === 'Movie' || item.Type === 'Episode') { if (item.PartCount && item.PartCount > 1 && (item.Type === 'Movie' || item.Type === 'Episode')) {
const client = ServerConnections.getApiClient(item.ServerId); const client = ServerConnections.getApiClient(item.ServerId);
const user = await client.getCurrentUser(); const user = await client.getCurrentUser();
const additionalParts = await client.getAdditionalVideoParts(user.Id, item.Id); const additionalParts = await client.getAdditionalVideoParts(user.Id, item.Id);
@ -2179,7 +2183,7 @@ class PlaybackManager {
// If it's still null then there's nothing to play // If it's still null then there's nothing to play
if (!firstItem) { if (!firstItem) {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); showPlaybackInfoErrorMessage(self, `PlaybackError.${MediaError.NO_MEDIA_ERROR}`);
return Promise.reject(); return Promise.reject();
} }
@ -2551,8 +2555,8 @@ class PlaybackManager {
onPlaybackStarted(player, playOptions, streamInfo, mediaSource); onPlaybackStarted(player, playOptions, streamInfo, mediaSource);
setTimeout(function () { setTimeout(function () {
onPlaybackError.call(player, err, { onPlaybackError.call(player, err, {
type: 'mediadecodeerror', type: getMediaError(err),
streamInfo: streamInfo streamInfo
}); });
}, 100); }, 100);
}); });
@ -2785,7 +2789,7 @@ class PlaybackManager {
return mediaSource; return mediaSource;
} }
} else { } else {
showPlaybackInfoErrorMessage(self, 'PlaybackErrorNoCompatibleStream'); showPlaybackInfoErrorMessage(self, `PlaybackError.${MediaError.NO_MEDIA_ERROR}`);
return Promise.reject(); return Promise.reject();
} }
}); });
@ -3194,22 +3198,32 @@ class PlaybackManager {
} }
} }
/**
* @param {object} streamInfo
* @param {MediaError} errorType
* @param {boolean} currentlyPreventsVideoStreamCopy
* @param {boolean} currentlyPreventsAudioStreamCopy
* @returns {boolean} Returns true if the stream should be retried by transcoding.
*/
function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) { function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) {
// mediadecodeerror, medianotsupported, network, servererror
return streamInfo.mediaSource.SupportsTranscoding return streamInfo.mediaSource.SupportsTranscoding
&& (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy); && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy);
} }
/**
* Playback error handler.
* @param {Error} e
* @param {object} error
* @param {object} error.streamInfo
* @param {MediaError} error.type
*/
function onPlaybackError(e, error) { function onPlaybackError(e, error) {
const player = this; const player = this;
error = error || {}; error = error || {};
// network
// mediadecodeerror
// medianotsupported
const errorType = error.type; const errorType = error.type;
console.debug('playbackmanager playback error type: ' + (errorType || '')); console.warn('[playbackmanager] onPlaybackError:', e, error);
const streamInfo = error.streamInfo || getPlayerData(player).streamInfo; const streamInfo = error.streamInfo || getPlayerData(player).streamInfo;
@ -3235,8 +3249,7 @@ class PlaybackManager {
Events.trigger(self, 'playbackerror', [errorType]); Events.trigger(self, 'playbackerror', [errorType]);
const displayErrorCode = 'NoCompatibleStream'; onPlaybackStopped.call(player, e, `.${errorType}`);
onPlaybackStopped.call(player, e, displayErrorCode);
} }
function onPlaybackStopped(e, displayErrorCode) { function onPlaybackStopped(e, displayErrorCode) {

View file

@ -179,6 +179,7 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) {
context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false; context.querySelector('.chkRememberAudioSelections').checked = user.Configuration.RememberAudioSelections || false;
context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false; context.querySelector('.chkRememberSubtitleSelections').checked = user.Configuration.RememberSubtitleSelections || false;
context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers();
context.querySelector('.chkLimitSupportedVideoResolution').checked = appSettings.limitSupportedVideoResolution();
setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); setMaxBitrateIntoField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
@ -194,8 +195,8 @@ function loadForm(context, user, userSettings, systemInfo, apiClient) {
selectChromecastVersion.innerHTML = ccAppsHtml; selectChromecastVersion.innerHTML = ccAppsHtml;
selectChromecastVersion.value = user.Configuration.CastReceiverId; selectChromecastVersion.value = user.Configuration.CastReceiverId;
const selectLabelMaxVideoWidth = context.querySelector('.selectLabelMaxVideoWidth'); const selectMaxVideoWidth = context.querySelector('.selectMaxVideoWidth');
selectLabelMaxVideoWidth.value = appSettings.maxVideoWidth(); selectMaxVideoWidth.value = appSettings.maxVideoWidth();
const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength'); const selectSkipForwardLength = context.querySelector('.selectSkipForwardLength');
fillSkipLengths(selectSkipForwardLength); fillSkipLengths(selectSkipForwardLength);
@ -212,7 +213,8 @@ function saveUser(context, user, userSettingsInstance, apiClient) {
appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked); appSettings.enableSystemExternalPlayers(context.querySelector('.chkExternalVideoPlayer').checked);
appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value); appSettings.maxChromecastBitrate(context.querySelector('.selectChromecastVideoQuality').value);
appSettings.maxVideoWidth(context.querySelector('.selectLabelMaxVideoWidth').value); appSettings.maxVideoWidth(context.querySelector('.selectMaxVideoWidth').value);
appSettings.limitSupportedVideoResolution(context.querySelector('.chkLimitSupportedVideoResolution').checked);
setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); setMaxBitrateFromField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video');
setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video'); setMaxBitrateFromField(context.querySelector('.selectVideoInternetQuality'), false, 'Video');
@ -309,7 +311,7 @@ function embed(options, self) {
options.element.querySelector('.btnSave').classList.remove('hide'); options.element.querySelector('.btnSave').classList.remove('hide');
} }
options.element.querySelector('.selectLabelMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self)); options.element.querySelector('.selectMaxVideoWidth').addEventListener('change', onMaxVideoWidthChange.bind(self));
self.loadData(); self.loadData();

View file

@ -43,7 +43,7 @@
</div> </div>
<div class="selectContainer"> <div class="selectContainer">
<select is="emby-select" class="selectLabelMaxVideoWidth" label="${LabelMaxVideoResolution}"> <select is="emby-select" class="selectMaxVideoWidth" label="${LabelMaxVideoResolution}">
<option value="0">${Auto}</option> <option value="0">${Auto}</option>
<option value="-1">${ScreenResolution}</option> <option value="-1">${ScreenResolution}</option>
<option value="640">360p</option> <option value="640">360p</option>
@ -54,6 +54,14 @@
<option value="7680">8K</option> <option value="7680">8K</option>
</select> </select>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input type="checkbox" is="emby-checkbox" class="chkLimitSupportedVideoResolution" />
<span>${LimitSupportedVideoResolution}</span>
</label>
<div class="fieldDescription checkboxFieldDescription">${LimitSupportedVideoResolutionHelp}</div>
</div>
</div> </div>
<div class="verticalSection verticalSection-extrabottompadding musicQualitySection hide"> <div class="verticalSection verticalSection-extrabottompadding musicQualitySection hide">

View file

@ -1,283 +0,0 @@
import escapeHtml from 'escape-html';
import dom from '../../scripts/dom';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager';
import { pluginManager } from '../pluginManager';
import * as userSettings from '../../scripts/settings/userSettings';
import { appRouter } from '../router/appRouter';
import globalize from '../../scripts/globalize';
import { PluginType } from '../../types/plugin.ts';
import '../../elements/emby-button/emby-button';
import '../../elements/emby-input/emby-input';
import '../../elements/emby-button/paper-icon-button-light';
import '../../elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
import ServerConnections from '../ServerConnections';
let currentServerId;
function onSubmit(e) {
const panel = dom.parentWithClass(this, 'dialog');
const playlistId = panel.querySelector('#selectPlaylistToAddTo').value;
const apiClient = ServerConnections.getApiClient(currentServerId);
if (playlistId) {
userSettings.set('playlisteditor-lastplaylistid', playlistId);
addToPlaylist(apiClient, panel, playlistId);
} else {
createPlaylist(apiClient, panel);
}
e.preventDefault();
return false;
}
function createPlaylist(apiClient, dlg) {
loading.show();
const url = apiClient.getUrl('Playlists', {
Name: dlg.querySelector('#txtNewPlaylistName').value,
Ids: dlg.querySelector('.fldSelectedItemIds').value || '',
userId: apiClient.getCurrentUserId()
});
apiClient.ajax({
type: 'POST',
url: url,
dataType: 'json',
contentType: 'application/json'
}).then(result => {
loading.hide();
const id = result.Id;
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToPlaylist(apiClient, id);
});
}
function redirectToPlaylist(apiClient, id) {
appRouter.showItem(id, apiClient.serverId());
}
function addToPlaylist(apiClient, dlg, id) {
const itemIds = dlg.querySelector('.fldSelectedItemIds').value || '';
if (id === 'queue') {
playbackManager.queue({
serverId: apiClient.serverId(),
ids: itemIds.split(',')
});
dlg.submitted = true;
dialogHelper.close(dlg);
return;
}
loading.show();
const url = apiClient.getUrl(`Playlists/${id}/Items`, {
Ids: itemIds,
userId: apiClient.getCurrentUserId()
});
apiClient.ajax({
type: 'POST',
url: url
}).then(() => {
loading.hide();
dlg.submitted = true;
dialogHelper.close(dlg);
});
}
function triggerChange(select) {
select.dispatchEvent(new CustomEvent('change', {}));
}
function populatePlaylists(editorOptions, panel) {
const select = panel.querySelector('#selectPlaylistToAddTo');
loading.hide();
panel.querySelector('.newPlaylistInfo').classList.add('hide');
const options = {
Recursive: true,
IncludeItemTypes: 'Playlist',
SortBy: 'SortName',
EnableTotalRecordCount: false
};
const apiClient = ServerConnections.getApiClient(currentServerId);
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
apiClient.getItems(apiClient.getCurrentUserId(), options).then(result => {
let html = '';
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
}
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += result.Items.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
});
select.innerHTML = html;
let defaultValue = editorOptions.defaultValue;
if (!defaultValue) {
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
}
select.value = defaultValue === 'new' ? '' : defaultValue;
// If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
if (!select.value) {
select.value = '';
}
triggerChange(select);
loading.hide();
});
}
function getEditorHtml(items) {
let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">';
html += '<div class="fldSelectPlaylist selectContainer">';
let autoFocus = items.length ? ' autofocus' : '';
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
html += '</div>';
html += '<div class="newPlaylistInfo">';
html += '<div class="inputContainer">';
autoFocus = items.length ? '' : ' autofocus';
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
html += '</div>';
// newPlaylistInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content, options, items) {
content.querySelector('#selectPlaylistToAddTo').addEventListener('change', function () {
if (this.value) {
content.querySelector('.newPlaylistInfo').classList.add('hide');
content.querySelector('#txtNewPlaylistName').removeAttribute('required');
} else {
content.querySelector('.newPlaylistInfo').classList.remove('hide');
content.querySelector('#txtNewPlaylistName').setAttribute('required', 'required');
}
});
content.querySelector('form').addEventListener('submit', onSubmit);
content.querySelector('.fldSelectedItemIds', content).value = items.join(',');
if (items.length) {
content.querySelector('.fldSelectPlaylist').classList.remove('hide');
populatePlaylists(options, content);
} else {
content.querySelector('.fldSelectPlaylist').classList.add('hide');
const selectPlaylistToAddTo = content.querySelector('#selectPlaylistToAddTo');
selectPlaylistToAddTo.innerHTML = '';
selectPlaylistToAddTo.value = '';
triggerChange(selectPlaylistToAddTo);
}
}
function centerFocus(elem, horiz, on) {
import('../../scripts/scrollHelper').then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
});
}
export class PlaylistEditor {
show(options) {
const items = options.items || {};
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false
};
if (layoutManager.tv) {
dialogOptions.size = 'fullscreen';
} else {
dialogOptions.size = 'small';
}
const dlg = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = globalize.translate('HeaderAddToPlaylist');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml(items);
dlg.innerHTML = html;
initEditor(dlg, options, items);
dlg.querySelector('.btnCancel').addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject();
});
}
}
export default PlaylistEditor;

View file

@ -0,0 +1,321 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
import { getItemsApi } from '@jellyfin/sdk/lib/utils/api/items-api';
import { getPlaylistsApi } from '@jellyfin/sdk/lib/utils/api/playlists-api';
import escapeHtml from 'escape-html';
import dom from 'scripts/dom';
import globalize from 'scripts/globalize';
import { currentSettings as userSettings } from 'scripts/settings/userSettings';
import { PluginType } from 'types/plugin';
import { toApi } from 'utils/jellyfin-apiclient/compat';
import dialogHelper from '../dialogHelper/dialogHelper';
import loading from '../loading/loading';
import layoutManager from '../layoutManager';
import { playbackManager } from '../playback/playbackmanager';
import { pluginManager } from '../pluginManager';
import { appRouter } from '../router/appRouter';
import ServerConnections from '../ServerConnections';
import 'elements/emby-button/emby-button';
import 'elements/emby-input/emby-input';
import 'elements/emby-button/paper-icon-button-light';
import 'elements/emby-select/emby-select';
import 'material-design-icons-iconfont';
import '../formdialog.scss';
interface DialogElement extends HTMLDivElement {
submitted?: boolean
}
interface PlaylistEditorOptions {
items: string[],
serverId: string,
enableAddToPlayQueue?: boolean,
defaultValue?: string
}
let currentServerId: string;
function onSubmit(this: HTMLElement, e: Event) {
const panel = dom.parentWithClass(this, 'dialog') as DialogElement | null;
if (panel) {
const playlistId = panel.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo')?.value;
loading.show();
if (playlistId) {
userSettings.set('playlisteditor-lastplaylistid', playlistId);
addToPlaylist(panel, playlistId)
.catch(err => {
console.error('[PlaylistEditor] Failed to add to playlist %s', playlistId, err);
})
.finally(loading.hide);
} else {
createPlaylist(panel)
.catch(err => {
console.error('[PlaylistEditor] Failed to create playlist', err);
})
.finally(loading.hide);
}
} else {
console.error('[PlaylistEditor] Dialog element is missing!');
}
e.preventDefault();
return false;
}
function createPlaylist(dlg: DialogElement) {
const apiClient = ServerConnections.getApiClient(currentServerId);
const api = toApi(apiClient);
const itemIds = dlg.querySelector<HTMLInputElement>('.fldSelectedItemIds')?.value || '';
return getPlaylistsApi(api)
.createPlaylist({
name: dlg.querySelector<HTMLInputElement>('#txtNewPlaylistName')?.value,
ids: itemIds.split(','),
userId: apiClient.getCurrentUserId()
})
.then(result => {
dlg.submitted = true;
dialogHelper.close(dlg);
redirectToPlaylist(result.data.Id);
});
}
function redirectToPlaylist(id: string | undefined) {
appRouter.showItem(id, currentServerId);
}
function addToPlaylist(dlg: DialogElement, id: string) {
const apiClient = ServerConnections.getApiClient(currentServerId);
const api = toApi(apiClient);
const itemIds = dlg.querySelector<HTMLInputElement>('.fldSelectedItemIds')?.value || '';
if (id === 'queue') {
playbackManager.queue({
serverId: currentServerId,
ids: itemIds.split(',')
});
dlg.submitted = true;
dialogHelper.close(dlg);
return Promise.resolve();
}
return getPlaylistsApi(api)
.addItemToPlaylist({
playlistId: id,
ids: itemIds.split(','),
userId: apiClient.getCurrentUserId()
})
.then(() => {
dlg.submitted = true;
dialogHelper.close(dlg);
});
}
function triggerChange(select: HTMLSelectElement) {
select.dispatchEvent(new CustomEvent('change', {}));
}
function populatePlaylists(editorOptions: PlaylistEditorOptions, panel: DialogElement) {
const select = panel.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo');
if (!select) {
return Promise.reject(new Error('Playlist <select> element is missing'));
}
loading.show();
panel.querySelector('.newPlaylistInfo')?.classList.add('hide');
const apiClient = ServerConnections.getApiClient(currentServerId);
const api = toApi(apiClient);
const SyncPlay = pluginManager.firstOfType(PluginType.SyncPlay)?.instance;
return getItemsApi(api)
.getItems({
userId: apiClient.getCurrentUserId(),
includeItemTypes: [ BaseItemKind.Playlist ],
sortBy: [ ItemSortBy.SortName ],
recursive: true
})
.then(({ data }) => {
let html = '';
if ((editorOptions.enableAddToPlayQueue !== false && playbackManager.isPlaying()) || SyncPlay?.Manager.isSyncPlayEnabled()) {
html += `<option value="queue">${globalize.translate('AddToPlayQueue')}</option>`;
}
html += `<option value="">${globalize.translate('OptionNew')}</option>`;
html += data.Items?.map(i => {
return `<option value="${i.Id}">${escapeHtml(i.Name)}</option>`;
});
select.innerHTML = html;
let defaultValue = editorOptions.defaultValue;
if (!defaultValue) {
defaultValue = userSettings.get('playlisteditor-lastplaylistid') || '';
}
select.value = defaultValue === 'new' ? '' : defaultValue;
// If the value is empty set it again, in case we tried to set a lastplaylistid that is no longer valid
if (!select.value) {
select.value = '';
}
triggerChange(select);
});
}
function getEditorHtml(items: string[]) {
let html = '';
html += '<div class="formDialogContent smoothScrollY" style="padding-top:2em;">';
html += '<div class="dialogContentInner dialog-content-centered">';
html += '<form style="margin:auto;">';
html += '<div class="fldSelectPlaylist selectContainer">';
let autoFocus = items.length ? ' autofocus' : '';
html += `<select is="emby-select" id="selectPlaylistToAddTo" label="${globalize.translate('LabelPlaylist')}"${autoFocus}></select>`;
html += '</div>';
html += '<div class="newPlaylistInfo">';
html += '<div class="inputContainer">';
autoFocus = items.length ? '' : ' autofocus';
html += `<input is="emby-input" type="text" id="txtNewPlaylistName" required="required" label="${globalize.translate('LabelName')}"${autoFocus} />`;
html += '</div>';
// newPlaylistInfo
html += '</div>';
html += '<div class="formDialogFooter">';
html += `<button is="emby-button" type="submit" class="raised btnSubmit block formDialogFooterItem button-submit">${globalize.translate('Add')}</button>`;
html += '</div>';
html += '<input type="hidden" class="fldSelectedItemIds" />';
html += '</form>';
html += '</div>';
html += '</div>';
return html;
}
function initEditor(content: DialogElement, options: PlaylistEditorOptions, items: string[]) {
content.querySelector('#selectPlaylistToAddTo')?.addEventListener('change', function(this: HTMLSelectElement) {
if (this.value) {
content.querySelector('.newPlaylistInfo')?.classList.add('hide');
content.querySelector('#txtNewPlaylistName')?.removeAttribute('required');
} else {
content.querySelector('.newPlaylistInfo')?.classList.remove('hide');
content.querySelector('#txtNewPlaylistName')?.setAttribute('required', 'required');
}
});
content.querySelector('form')?.addEventListener('submit', onSubmit);
const selectedItemsInput = content.querySelector<HTMLInputElement>('.fldSelectedItemIds');
if (selectedItemsInput) {
selectedItemsInput.value = items.join(',');
}
if (items.length) {
content.querySelector('.fldSelectPlaylist')?.classList.remove('hide');
populatePlaylists(options, content)
.catch(err => {
console.error('[PlaylistEditor] failed to populate playlists', err);
})
.finally(loading.hide);
} else {
content.querySelector('.fldSelectPlaylist')?.classList.add('hide');
const selectPlaylistToAddTo = content.querySelector<HTMLSelectElement>('#selectPlaylistToAddTo');
if (selectPlaylistToAddTo) {
selectPlaylistToAddTo.innerHTML = '';
selectPlaylistToAddTo.value = '';
triggerChange(selectPlaylistToAddTo);
}
}
}
function centerFocus(elem: HTMLDivElement | null, horiz: boolean, on: boolean) {
if (!elem) {
console.error('[PlaylistEditor] cannot focus null element');
return;
}
import('../../scripts/scrollHelper')
.then((scrollHelper) => {
const fn = on ? 'on' : 'off';
scrollHelper.centerFocus[fn](elem, horiz);
})
.catch(err => {
console.error('[PlaylistEditor] failed to load scroll helper', err);
});
}
export class PlaylistEditor {
show(options: PlaylistEditorOptions) {
const items = options.items || [];
currentServerId = options.serverId;
const dialogOptions = {
removeOnClose: true,
scrollY: false,
size: layoutManager.tv ? 'fullscreen' : 'small'
};
const dlg: DialogElement = dialogHelper.createDialog(dialogOptions);
dlg.classList.add('formDialog');
let html = '';
const title = globalize.translate('HeaderAddToPlaylist');
html += '<div class="formDialogHeader">';
html += `<button is="paper-icon-button-light" class="btnCancel autoSize" tabindex="-1" title="${globalize.translate('ButtonBack')}"><span class="material-icons arrow_back" aria-hidden="true"></span></button>`;
html += '<h3 class="formDialogHeaderTitle">';
html += title;
html += '</h3>';
html += '</div>';
html += getEditorHtml(items);
dlg.innerHTML = html;
initEditor(dlg, options, items);
dlg.querySelector('.btnCancel')?.addEventListener('click', () => {
dialogHelper.close(dlg);
});
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, true);
}
return dialogHelper.open(dlg).then(() => {
if (layoutManager.tv) {
centerFocus(dlg.querySelector('.formDialogContent'), false, false);
}
if (dlg.submitted) {
return Promise.resolve();
}
return Promise.reject(new Error());
});
}
}
export default PlaylistEditor;

View file

@ -222,7 +222,8 @@ function updateNowPlayingInfo(context, state, serverId) {
contextButton.addEventListener('click', function () { contextButton.addEventListener('click', function () {
itemContextMenu.show(Object.assign({ itemContextMenu.show(Object.assign({
item: fullItem, item: fullItem,
user: user user: user,
isMobile: layoutManager.mobile
}, options)) }, options))
.catch(() => { /* no-op */ }); .catch(() => { /* no-op */ });
}); });
@ -233,8 +234,8 @@ function updateNowPlayingInfo(context, state, serverId) {
apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) { apiClient.getItem(apiClient.getCurrentUserId(), item.Id).then(function (fullItem) {
const userData = fullItem.UserData || {}; const userData = fullItem.UserData || {};
const likes = userData.Likes == null ? '' : userData.Likes; const likes = userData.Likes == null ? '' : userData.Likes;
context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>'; context.querySelector('.nowPlayingPageUserDataButtonsTitle').innerHTML = '<button is="emby-ratingbutton" type="button" class="paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = '<button is="emby-ratingbutton" type="button" class="listItemButton paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>'; context.querySelector('.nowPlayingPageUserDataButtons').innerHTML = '<button is="emby-ratingbutton" type="button" class="paper-icon-button-light" data-id="' + fullItem.Id + '" data-serverid="' + fullItem.ServerId + '" data-itemtype="' + fullItem.Type + '" data-likes="' + likes + '" data-isfavorite="' + userData.IsFavorite + '"><span class="material-icons favorite" aria-hidden="true"></span></button>';
}); });
} else { } else {
clearBackdrop(); clearBackdrop();
@ -323,6 +324,7 @@ export default function () {
context.querySelector('.remoteControlSection').classList.add('hide'); context.querySelector('.remoteControlSection').classList.add('hide');
} }
buttonVisible(context.querySelector('.btnLyrics'), item?.Type === 'Audio' && !layoutManager.mobile);
buttonVisible(context.querySelector('.btnStop'), item != null); buttonVisible(context.querySelector('.btnStop'), item != null);
buttonVisible(context.querySelector('.btnNextTrack'), item != null); buttonVisible(context.querySelector('.btnNextTrack'), item != null);
buttonVisible(context.querySelector('.btnPreviousTrack'), item != null); buttonVisible(context.querySelector('.btnPreviousTrack'), item != null);
@ -769,6 +771,10 @@ export default function () {
playbackManager.fastForward(currentPlayer); playbackManager.fastForward(currentPlayer);
} }
}); });
context.querySelector('.btnLyrics').addEventListener('click', function () {
appRouter.show('lyrics');
});
for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) { for (const shuffleButton of context.querySelectorAll('.btnShuffleQueue')) {
shuffleButton.addEventListener('click', function () { shuffleButton.addEventListener('click', function () {
if (currentPlayer) { if (currentPlayer) {

View file

@ -351,16 +351,16 @@
.nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button { .nowPlayingInfoControls .nowPlayingPageUserDataButtonsTitle button {
padding-top: 0; padding-top: 0;
border-radius: 0; border-radius: 0;
margin-left: 0;
margin-right: 0;
[dir="ltr"] & { [dir="ltr"] & {
padding-right: 0; padding-right: 0;
margin-right: 0;
float: right; float: right;
} }
[dir="rtl"] & { [dir="rtl"] & {
padding-left: 0; padding-left: 0;
margin-left: 0;
float: left; float: left;
} }
} }

View file

@ -4,7 +4,7 @@
*/ */
import dom from '../scripts/dom'; import dom from '../scripts/dom';
import browser from '../scripts/browser'; import appSettings from 'scripts/settings/appSettings';
import layoutManager from './layoutManager'; import layoutManager from './layoutManager';
/** /**
@ -477,7 +477,7 @@ function doScroll(xScroller, scrollX, yScroller, scrollY, smooth) {
* Returns true if smooth scroll must be used. * Returns true if smooth scroll must be used.
*/ */
function useSmoothScroll() { function useSmoothScroll() {
return !!browser.tizen; return appSettings.enableSmoothScroll();
} }
/** /**

View file

@ -34,7 +34,7 @@ const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId
useEffect(() => { useEffect(() => {
if (api && user?.Id) { if (api && user?.Id) {
getItemsApi(api) getItemsApi(api)
.getItemsByUserId({ .getItems({
userId: user.Id, userId: user.Id,
sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random], sortBy: [ItemSortBy.IsFavoriteOrLiked, ItemSortBy.Random],
includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series, BaseItemKind.MusicArtist], includeItemTypes: [BaseItemKind.Movie, BaseItemKind.Series, BaseItemKind.MusicArtist],

View file

@ -47,15 +47,15 @@
<span>MPEG1</span> <span>MPEG1</span>
</label> </label>
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,nvenc,qsv,vaapi,rkmpp,videotoolbox" /> <input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg2video" data-types="amf,nvenc,qsv,vaapi,rkmpp" />
<span>MPEG2</span> <span>MPEG2</span>
</label> </label>
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="nvenc,rkmpp,videotoolbox" /> <input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="mpeg4" data-types="nvenc,rkmpp" />
<span>MPEG4</span> <span>MPEG4</span>
</label> </label>
<label> <label>
<input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,nvenc,qsv,vaapi,videotoolbox" /> <input type="checkbox" is="emby-checkbox" class="chkDecodeCodec" data-codec="vc1" data-types="amf,nvenc,qsv,vaapi" />
<span>VC1</span> <span>VC1</span>
</label> </label>
<label> <label>
@ -123,24 +123,20 @@
</div> </div>
<div class="checkboxListContainer"> <div class="checkboxListContainer">
<h3 class="checkboxListLabel">${LabelEncodingFormatOptions}</h3>
<div class="fieldDescription">${EncodingFormatHelp}</div>
<div class="checkboxList"> <div class="checkboxList">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkAllowHevcEncoding" /> <input type="checkbox" is="emby-checkbox" id="chkAllowHevcEncoding" />
<span>${AllowHevcEncoding}</span> <span>${AllowHevcEncoding}</span>
</label> </label>
</div> </div>
<div class="checkboxList allowAv1EncodingOption"> <div class="checkboxList">
<label> <label>
<input type="checkbox" is="emby-checkbox" id="chkAllowAv1Encoding" /> <input type="checkbox" is="emby-checkbox" id="chkAllowAv1Encoding" />
<span>${AllowAv1Encoding}</span> <span>${AllowAv1Encoding}</span>
</label> </label>
</div> </div>
<div class="checkboxList">
<label>
<input type="checkbox" is="emby-checkbox" id="chkAllowMjpegEncoding" />
<span>${AllowMjpegEncoding}</span>
</label>
</div>
</div> </div>
<div class="vppTonemappingOptions hide"> <div class="vppTonemappingOptions hide">
@ -251,9 +247,8 @@
<div class="inputContainer fldEncoderPath"> <div class="inputContainer fldEncoderPath">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<div style="flex-grow:1;"> <div style="flex-grow:1;">
<input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" dir="ltr" /> <input is="emby-input" class="txtEncoderPath" label="${LabelffmpegPath}" autocomplete="off" dir="ltr" disabled/>
</div> </div>
<button type="button" is="paper-icon-button-light" id="btnSelectEncoderPath" class="emby-input-iconbutton"><span class="material-icons search" aria-hidden="true"></span></button>
</div> </div>
<div class="fieldDescription"> <div class="fieldDescription">
<div>${LabelffmpegPathHelp}</div> <div>${LabelffmpegPathHelp}</div>

View file

@ -19,7 +19,6 @@ function loadPage(page, config, systemInfo) {
page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding; page.querySelector('#chkHardwareEncoding').checked = config.EnableHardwareEncoding;
page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding; page.querySelector('#chkAllowHevcEncoding').checked = config.AllowHevcEncoding;
page.querySelector('#chkAllowAv1Encoding').checked = config.AllowAv1Encoding; page.querySelector('#chkAllowAv1Encoding').checked = config.AllowAv1Encoding;
page.querySelector('#chkAllowMjpegEncoding').checked = config.AllowMjpegEncoding;
$('#selectVideoDecoder', page).val(config.HardwareAccelerationType); $('#selectVideoDecoder', page).val(config.HardwareAccelerationType);
$('#selectThreadCount', page).val(config.EncodingThreadCount); $('#selectThreadCount', page).val(config.EncodingThreadCount);
page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr; page.querySelector('#chkEnableAudioVbr').checked = config.EnableAudioVbr;
@ -128,7 +127,6 @@ function onSubmit() {
config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked; config.EnableHardwareEncoding = form.querySelector('#chkHardwareEncoding').checked;
config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked; config.AllowHevcEncoding = form.querySelector('#chkAllowHevcEncoding').checked;
config.AllowAv1Encoding = form.querySelector('#chkAllowAv1Encoding').checked; config.AllowAv1Encoding = form.querySelector('#chkAllowAv1Encoding').checked;
config.AllowMjpegEncoding = form.querySelector('#chkAllowMjpegEncoding').checked;
ApiClient.updateNamedConfiguration('encoding', config).then(function () { ApiClient.updateNamedConfiguration('encoding', config).then(function () {
updateEncoder(form); updateEncoder(form);
}, function () { }, function () {
@ -227,13 +225,11 @@ $(document).on('pageinit', '#encodingSettingsPage', function () {
if (this.value === 'videotoolbox') { if (this.value === 'videotoolbox') {
page.querySelector('.videoToolboxTonemappingOptions').classList.remove('hide'); page.querySelector('.videoToolboxTonemappingOptions').classList.remove('hide');
page.querySelector('.allowAv1EncodingOption').classList.add('hide');
} else { } else {
page.querySelector('.videoToolboxTonemappingOptions').classList.add('hide'); page.querySelector('.videoToolboxTonemappingOptions').classList.add('hide');
page.querySelector('.allowAv1EncodingOption').classList.remove('hide');
} }
if (systemInfo.OperatingSystem.toLowerCase() === 'linux' && (this.value == 'qsv' || this.value == 'vaapi')) { if (this.value == 'qsv' || this.value == 'vaapi') {
page.querySelector('.vppTonemappingOptions').classList.remove('hide'); page.querySelector('.vppTonemappingOptions').classList.remove('hide');
} else { } else {
page.querySelector('.vppTonemappingOptions').classList.add('hide'); page.querySelector('.vppTonemappingOptions').classList.add('hide');
@ -259,21 +255,6 @@ $(document).on('pageinit', '#encodingSettingsPage', function () {
setDecodingCodecsVisible(page, this.value); setDecodingCodecsVisible(page, this.value);
}); });
$('#btnSelectEncoderPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser();
picker.show({
includeFiles: true,
callback: function (path) {
if (path) {
$('.txtEncoderPath', page).val(path);
}
picker.close();
}
});
});
});
$('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () { $('#btnSelectTranscodingTempPath', page).on('click.selectDirectory', function () {
import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => { import('../../components/directorybrowser/directorybrowser').then(({ default: DirectoryBrowser }) => {
const picker = new DirectoryBrowser(); const picker = new DirectoryBrowser();

View file

@ -67,7 +67,7 @@ function loadPage(page) {
const config = responses[0]; const config = responses[0];
page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || ''; page.querySelector('#selectLanguage').value = config.PreferredMetadataLanguage || '';
page.querySelector('#selectCountry').value = config.MetadataCountryCode || ''; page.querySelector('#selectCountry').value = config.MetadataCountryCode || '';
page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || ''; page.querySelector('#valDummyChapterDuration').value = config.DummyChapterDuration || '0';
page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || ''; page.querySelector('#txtChapterImageResolution').value = config.ChapterImageResolution || '';
loading.hide(); loading.hide();
}); });

View file

@ -3,11 +3,14 @@ import markdownIt from 'markdown-it';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import loading from '../../../../components/loading/loading'; import loading from '../../../../components/loading/loading';
import globalize from '../../../../scripts/globalize'; import globalize from '../../../../scripts/globalize';
import '../../../../elements/emby-button/emby-button';
import Dashboard from '../../../../utils/dashboard'; import Dashboard from '../../../../utils/dashboard';
import alert from '../../../../components/alert'; import alert from '../../../../components/alert';
import confirm from '../../../../components/confirm/confirm'; import confirm from '../../../../components/confirm/confirm';
import 'elements/emby-button/emby-button';
import 'elements/emby-collapse/emby-collapse';
import 'elements/emby-select/emby-select';
function populateHistory(packageInfo, page) { function populateHistory(packageInfo, page) {
let html = ''; let html = '';
const length = Math.min(packageInfo.versions.length, 10); const length = Math.min(packageInfo.versions.length, 10);

View file

@ -7,6 +7,7 @@ import ServerConnections from 'components/ServerConnections';
import dom from 'scripts/dom'; import dom from 'scripts/dom';
import globalize from 'scripts/globalize'; import globalize from 'scripts/globalize';
import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card'; import { getBackdropShape, getPortraitShape, getSquareShape } from 'utils/card';
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
import 'elements/emby-itemscontainer/emby-itemscontainer'; import 'elements/emby-itemscontainer/emby-itemscontainer';
import 'elements/emby-scroller/emby-scroller'; import 'elements/emby-scroller/emby-scroller';
@ -133,7 +134,7 @@ function getFetchDataFn(section) {
return function () { return function () {
const apiClient = this.apiClient; const apiClient = this.apiClient;
const options = { const options = {
SortBy: 'SeriesName,SortName', SortBy: [ItemSortBy.SeriesSortName, ItemSortBy.SortName].join(','),
SortOrder: 'Ascending', SortOrder: 'Ascending',
Filters: 'IsFavorite', Filters: 'IsFavorite',
Recursive: true, Recursive: true,

View file

@ -187,6 +187,11 @@
</div> </div>
</div> </div>
<div id="lyricsSection" class="verticalSection-extrabottompadding detailVerticalSection lyricsContainer hide">
<h2 class="sectionTitle sectionTitle-cards padded-right">${Lyrics}</h2>
<div is="emby-itemscontainer" class="vertical-list itemsContainer"></div>
</div>
<div class="verticalSection detailVerticalSection moreFromArtistSection hide"> <div class="verticalSection detailVerticalSection moreFromArtistSection hide">
<h2 class="sectionTitle sectionTitle-cards padded-right"></h2> <h2 class="sectionTitle sectionTitle-cards padded-right"></h2>
<div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true"> <div is="emby-scroller" class="padded-top-focusscale padded-bottom-focusscale" data-centerfocus="true">

View file

@ -1,7 +1,7 @@
import { intervalToDuration } from 'date-fns'; import { intervalToDuration } from 'date-fns';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import markdownIt from 'markdown-it';
import escapeHtml from 'escape-html'; import escapeHtml from 'escape-html';
import markdownIt from 'markdown-it';
import isEqual from 'lodash-es/isEqual'; import isEqual from 'lodash-es/isEqual';
import { appHost } from 'components/apphost'; import { appHost } from 'components/apphost';
@ -1055,6 +1055,7 @@ function renderDetails(page, item, apiClient, context) {
renderOverview(page, item); renderOverview(page, item);
renderMiscInfo(page, item); renderMiscInfo(page, item);
reloadUserDataButtons(page, item); reloadUserDataButtons(page, item);
renderLyricsContainer(page, item, apiClient);
// Don't allow redirection to other websites from the TV layout // Don't allow redirection to other websites from the TV layout
if (!layoutManager.tv && appHost.supports('externallinks')) { if (!layoutManager.tv && appHost.supports('externallinks')) {
@ -1069,6 +1070,38 @@ function enableScrollX() {
return browser.mobile && window.screen.availWidth <= 1000; return browser.mobile && window.screen.availWidth <= 1000;
} }
function renderLyricsContainer(view, item, apiClient) {
const lyricContainer = view.querySelector('.lyricsContainer');
if (lyricContainer && item.HasLyrics) {
if (item.Type !== 'Audio') {
lyricContainer.classList.add('hide');
return;
}
//get lyrics
apiClient.ajax({
url: apiClient.getUrl('Audio/' + item.Id + '/Lyrics'),
type: 'GET',
dataType: 'json'
}).then((response) => {
if (!response.Lyrics) {
lyricContainer.classList.add('hide');
return;
}
lyricContainer.classList.remove('hide');
const itemsContainer = lyricContainer.querySelector('.itemsContainer');
if (itemsContainer) {
const html = response.Lyrics.reduce((htmlAccumulator, lyric) => {
htmlAccumulator += escapeHtml(lyric.Text) + '<br/>';
return htmlAccumulator;
}, '');
itemsContainer.innerHTML = html;
}
}).catch(() => {
lyricContainer.classList.add('hide');
});
}
}
function renderMoreFromSeason(view, item, apiClient) { function renderMoreFromSeason(view, item, apiClient) {
const section = view.querySelector('.moreFromSeasonSection'); const section = view.querySelector('.moreFromSeasonSection');
@ -1119,7 +1152,7 @@ function renderMoreFromArtist(view, item, apiClient) {
const section = view.querySelector('.moreFromArtistSection'); const section = view.querySelector('.moreFromArtistSection');
if (section) { if (section) {
if (item.Type !== 'MusicArtist' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) { if (item.Type !== 'MusicArtist' && item.Type !== 'Audio' && (item.Type !== 'MusicAlbum' || !item.AlbumArtists || !item.AlbumArtists.length)) {
section.classList.add('hide'); section.classList.add('hide');
return; return;
} }
@ -1174,7 +1207,7 @@ function renderSimilarItems(page, item, context) {
const similarCollapsible = page.querySelector('#similarCollapsible'); const similarCollapsible = page.querySelector('#similarCollapsible');
if (similarCollapsible) { if (similarCollapsible) {
if (item.Type != 'Movie' && item.Type != 'Trailer' && item.Type != 'Series' && item.Type != 'Program' && item.Type != 'Recording' && item.Type != 'MusicAlbum' && item.Type != 'MusicArtist' && item.Type != 'Playlist') { if (item.Type != 'Movie' && item.Type != 'Trailer' && item.Type != 'Series' && item.Type != 'Program' && item.Type != 'Recording' && item.Type != 'MusicAlbum' && item.Type != 'MusicArtist' && item.Type != 'Playlist' && item.Type != 'Audio') {
similarCollapsible.classList.add('hide'); similarCollapsible.classList.add('hide');
return; return;
} }

View file

@ -13,6 +13,7 @@ import '../elements/emby-scroller/emby-scroller';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import LibraryMenu from '../scripts/libraryMenu'; import LibraryMenu from '../scripts/libraryMenu';
import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type'; import { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
import { ItemSortBy } from '@jellyfin/sdk/lib/generated-client/models/item-sort-by';
function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) { function getInitialLiveTvQuery(instance, params, startIndex = 0, limit = 300) {
const query = { const query = {
@ -223,7 +224,7 @@ function updateAlphaPickerState(instance) {
if (alphaPicker) { if (alphaPicker) {
const values = instance.getSortValues(); const values = instance.getSortValues();
if (values.sortBy.indexOf('SortName') !== -1) { if (values.sortBy.indexOf(ItemSortBy.SortName) !== -1) {
alphaPicker.classList.remove('hide'); alphaPicker.classList.remove('hide');
instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker'); instance.itemsContainer.parentNode.classList.add('padded-right-withalphapicker');
} else { } else {
@ -981,7 +982,7 @@ class ItemsView {
return sortNameOption.value; return sortNameOption.value;
} }
return 'IsFolder,' + sortNameOption.value; return `${ItemSortBy.IsFolder},${sortNameOption.value}`;
} }
getSortMenuOptions() { getSortMenuOptions() {
@ -990,7 +991,7 @@ class ItemsView {
if (this.params.type === 'Programs') { if (this.params.type === 'Programs') {
sortBy.push({ sortBy.push({
name: globalize.translate('AirDate'), name: globalize.translate('AirDate'),
value: 'StartDate,SortName' value: [ItemSortBy.StartDate, ItemSortBy.SortName].join(',')
}); });
} }
@ -1015,7 +1016,7 @@ class ItemsView {
if (this.params.type !== 'Programs') { if (this.params.type !== 'Programs') {
sortBy.push({ sortBy.push({
name: globalize.translate('DateAdded'), name: globalize.translate('DateAdded'),
value: 'DateCreated,SortName' value: [ItemSortBy.DateCreated, ItemSortBy.SortName].join(',')
}); });
} }
@ -1029,13 +1030,13 @@ class ItemsView {
option = this.getNameSortOption(this.params); option = this.getNameSortOption(this.params);
sortBy.push({ sortBy.push({
name: globalize.translate('Folders'), name: globalize.translate('Folders'),
value: 'IsFolder,' + option.value value: `${ItemSortBy.IsFolder},${option.value}`
}); });
} }
sortBy.push({ sortBy.push({
name: globalize.translate('ParentalRating'), name: globalize.translate('ParentalRating'),
value: 'OfficialRating,SortName' value: [ItemSortBy.OfficialRating, ItemSortBy.SortName].join(',')
}); });
option = this.getPlayCountSortOption(); option = this.getPlayCountSortOption();
@ -1045,11 +1046,11 @@ class ItemsView {
sortBy.push({ sortBy.push({
name: globalize.translate('ReleaseDate'), name: globalize.translate('ReleaseDate'),
value: 'ProductionYear,PremiereDate,SortName' value: [ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName].join(',')
}); });
sortBy.push({ sortBy.push({
name: globalize.translate('Runtime'), name: globalize.translate('Runtime'),
value: 'Runtime,SortName' value: [ItemSortBy.Runtime, ItemSortBy.SortName].join(',')
}); });
return sortBy; return sortBy;
} }
@ -1058,13 +1059,13 @@ class ItemsView {
if (params.type === 'Episode') { if (params.type === 'Episode') {
return { return {
name: globalize.translate('Name'), name: globalize.translate('Name'),
value: 'SeriesName,SortName' value: [ItemSortBy.SeriesSortName, ItemSortBy.SortName].join(',')
}; };
} }
return { return {
name: globalize.translate('Name'), name: globalize.translate('Name'),
value: 'SortName' value: ItemSortBy.SortName
}; };
} }
@ -1075,7 +1076,7 @@ class ItemsView {
return { return {
name: globalize.translate('PlayCount'), name: globalize.translate('PlayCount'),
value: 'PlayCount,SortName' value: [ItemSortBy.PlayCount, ItemSortBy.SortName].join(',')
}; };
} }
@ -1086,7 +1087,7 @@ class ItemsView {
return { return {
name: globalize.translate('DatePlayed'), name: globalize.translate('DatePlayed'),
value: 'DatePlayed,SortName' value: [ItemSortBy.DatePlayed, ItemSortBy.SortName].join(',')
}; };
} }
@ -1097,14 +1098,14 @@ class ItemsView {
return { return {
name: globalize.translate('CriticRating'), name: globalize.translate('CriticRating'),
value: 'CriticRating,SortName' value: [ItemSortBy.CriticRating, ItemSortBy.SortName].join(',')
}; };
} }
getCommunityRatingSortOption() { getCommunityRatingSortOption() {
return { return {
name: globalize.translate('CommunityRating'), name: globalize.translate('CommunityRating'),
value: 'CommunityRating,SortName' value: [ItemSortBy.CommunityRating, ItemSortBy.SortName].join(',')
}; };
} }

View file

@ -0,0 +1,6 @@
<div id="lyricPage" data-role="page" class="page lyricPage" data-backbutton="true">
<div>
<div class="dynamicLyricsContainer padded-bottom-page">
</div>
</div>
</div>

250
src/controllers/lyrics.js Normal file
View file

@ -0,0 +1,250 @@
import escapeHtml from 'escape-html';
import autoFocuser from 'components/autoFocuser';
import { appRouter } from '../components/router/appRouter';
import layoutManager from 'components/layoutManager';
import { playbackManager } from '../components/playback/playbackmanager';
import ServerConnections from '../components/ServerConnections';
import globalize from '../scripts/globalize';
import LibraryMenu from '../scripts/libraryMenu';
import Events from '../utils/events.ts';
import '../styles/lyrics.scss';
let currentPlayer;
let currentItem;
let savedLyrics;
let isDynamicLyric = false;
function dynamicLyricHtmlReducer(htmlAccumulator, lyric, index) {
if (layoutManager.tv) {
htmlAccumulator += `<button class="lyricsLine dynamicLyric listItem show-focus" id="lyricPosition${index}" data-lyrictime="${lyric.Start}">${escapeHtml(lyric.Text)}</button>`;
} else {
htmlAccumulator += `<div class="lyricsLine dynamicLyric" id="lyricPosition${index}" data-lyrictime="${lyric.Start}">${escapeHtml(lyric.Text)}</div>`;
}
return htmlAccumulator;
}
function staticLyricHtmlReducer(htmlAccumulator, lyric, index) {
if (layoutManager.tv) {
htmlAccumulator += `<button class="lyricsLine listItem show-focus" id="lyricPosition${index}">${escapeHtml(lyric.Text)}</button>`;
} else {
htmlAccumulator += `<div class="lyricsLine" id="lyricPosition${index}">${escapeHtml(lyric.Text)}</div>`;
}
return htmlAccumulator;
}
function getLyricIndex(time, lyrics) {
return lyrics.findLastIndex(lyric => lyric.Start <= time);
}
function getCurrentPlayTime() {
let currentTime = playbackManager.currentTime();
if (currentTime === undefined) currentTime = 0;
//convert to ticks
return currentTime * 10000;
}
export default function (view) {
function setPastLyricClassOnLine(line) {
const lyric = view.querySelector(`#lyricPosition${line}`);
if (lyric) {
lyric.classList.remove('futureLyric');
lyric.classList.add('pastLyric');
}
}
function setFutureLyricClassOnLine(line) {
const lyric = view.querySelector(`#lyricPosition${line}`);
if (lyric) {
lyric.classList.remove('pastLyric');
lyric.classList.add('futureLyric');
}
}
function setCurrentLyricClassOnLine(line) {
const lyric = view.querySelector(`#lyricPosition${line}`);
if (lyric) {
lyric.classList.remove('pastLyric');
lyric.classList.remove('futureLyric');
}
}
function updateAllLyricLines(currentLine, lyrics) {
for (let lyricIndex = 0; lyricIndex <= lyrics.length; lyricIndex++) {
if (lyricIndex < currentLine) {
setPastLyricClassOnLine(lyricIndex);
} else if (lyricIndex === currentLine) {
setCurrentLyricClassOnLine(lyricIndex);
} else if (lyricIndex > currentLine) {
setFutureLyricClassOnLine(lyricIndex);
}
}
}
function renderNoLyricMessage() {
const itemsContainer = view.querySelector('.dynamicLyricsContainer');
if (itemsContainer) {
const html = `<h1> ${globalize.translate('HeaderNoLyrics')} </h1>`;
itemsContainer.innerHTML = html;
}
autoFocuser.autoFocus();
}
function renderDynamicLyrics(lyrics) {
const itemsContainer = view.querySelector('.dynamicLyricsContainer');
if (itemsContainer) {
const html = lyrics.reduce(dynamicLyricHtmlReducer, '');
itemsContainer.innerHTML = html;
}
const lyricLineArray = itemsContainer.querySelectorAll('.lyricsLine');
// attaches click event listener to change playtime to lyric start
lyricLineArray.forEach(element => {
element.addEventListener('click', () => onLyricClick(element.getAttribute('data-lyrictime')));
});
const currentIndex = getLyricIndex(getCurrentPlayTime(), lyrics);
updateAllLyricLines(currentIndex, savedLyrics);
}
function renderStaticLyrics(lyrics) {
const itemsContainer = view.querySelector('.dynamicLyricsContainer');
if (itemsContainer) {
const html = lyrics.reduce(staticLyricHtmlReducer, '');
itemsContainer.innerHTML = html;
}
}
function updateLyrics(lyrics) {
savedLyrics = lyrics;
isDynamicLyric = Object.prototype.hasOwnProperty.call(lyrics[0], 'Start');
if (isDynamicLyric) {
renderDynamicLyrics(savedLyrics);
} else {
renderStaticLyrics(savedLyrics);
}
autoFocuser.autoFocus(view);
}
function getLyrics(serverId, itemId) {
const apiClient = ServerConnections.getApiClient(serverId);
return apiClient.ajax({
url: apiClient.getUrl('Audio/' + itemId + '/Lyrics'),
type: 'GET',
dataType: 'json'
}).then((response) => {
if (!response.Lyrics) {
throw new Error();
}
return response.Lyrics;
});
}
function bindToPlayer(player) {
if (player === currentPlayer) {
return;
}
releaseCurrentPlayer();
currentPlayer = player;
if (!player) {
return;
}
Events.on(player, 'timeupdate', onTimeUpdate);
Events.on(player, 'playbackstart', onPlaybackStart);
Events.on(player, 'playbackstop', onPlaybackStop);
}
function releaseCurrentPlayer() {
const player = currentPlayer;
if (player) {
Events.off(player, 'timeupdate', onTimeUpdate);
Events.off(player, 'playbackstart', onPlaybackStart);
Events.off(player, 'playbackstop', onPlaybackStop);
currentPlayer = null;
}
}
function onLyricClick(lyricTime) {
playbackManager.seek(lyricTime);
if (playbackManager.paused()) {
playbackManager.playPause(currentPlayer);
}
}
function onTimeUpdate() {
if (isDynamicLyric) {
const currentIndex = getLyricIndex(getCurrentPlayTime(), savedLyrics);
updateAllLyricLines(currentIndex, savedLyrics);
}
}
function onPlaybackStart(event, state) {
if (currentItem.Id !== state.NowPlayingItem.Id) {
onLoad();
}
}
function onPlaybackStop(_, state) {
// TODO: switch to appRouter.back(), with fix to navigation to /#/queue. Which is broken when it has nothing playing
if (!state.NextMediaType) {
appRouter.goHome();
}
}
function onPlayerChange() {
const player = playbackManager.getCurrentPlayer();
bindToPlayer(player);
}
function onLoad() {
savedLyrics = null;
currentItem = null;
isDynamicLyric = false;
LibraryMenu.setTitle(globalize.translate('Lyrics'));
const player = playbackManager.getCurrentPlayer();
if (player) {
bindToPlayer(player);
const state = playbackManager.getPlayerState(player);
currentItem = state.NowPlayingItem;
const serverId = state.NowPlayingItem.ServerId;
const itemId = state.NowPlayingItem.Id;
getLyrics(serverId, itemId).then(updateLyrics).catch(renderNoLyricMessage);
} else {
// if nothing is currently playing, no lyrics to display redirect to home
appRouter.goHome();
}
}
view.addEventListener('viewshow', function () {
Events.on(playbackManager, 'playerchange', onPlayerChange);
try {
onLoad();
} catch (e) {
appRouter.goHome();
}
});
view.addEventListener('viewbeforehide', function () {
Events.off(playbackManager, 'playerchange', onPlayerChange);
releaseCurrentPlayer();
});
}

View file

@ -20,7 +20,6 @@ export default function (view, params, tabContent) {
}, },
view: userSettings.getSavedView(key) || 'Poster' view: userSettings.getSavedView(key) || 'Poster'
}; };
pageData.query.ParentId = params.topParentId;
userSettings.loadQuerySettings(key, pageData.query); userSettings.loadQuerySettings(key, pageData.query);
} }

View file

@ -86,7 +86,7 @@ function loadRecentlyPlayed(page, parentId) {
IncludeItemTypes: 'Audio', IncludeItemTypes: 'Audio',
Limit: itemsPerRow(), Limit: itemsPerRow(),
Recursive: true, Recursive: true,
Fields: 'PrimaryImageAspectRatio,AudioInfo', Fields: 'PrimaryImageAspectRatio',
Filters: 'IsPlayed', Filters: 'IsPlayed',
ParentId: parentId, ParentId: parentId,
ImageTypeLimit: 1, ImageTypeLimit: 1,
@ -128,7 +128,7 @@ function loadFrequentlyPlayed(page, parentId) {
IncludeItemTypes: 'Audio', IncludeItemTypes: 'Audio',
Limit: itemsPerRow(), Limit: itemsPerRow(),
Recursive: true, Recursive: true,
Fields: 'PrimaryImageAspectRatio,AudioInfo', Fields: 'PrimaryImageAspectRatio',
Filters: 'IsPlayed', Filters: 'IsPlayed',
ParentId: parentId, ParentId: parentId,
ImageTypeLimit: 1, ImageTypeLimit: 1,
@ -399,4 +399,3 @@ export default function (view, params) {
}); });
}); });
} }

View file

@ -23,7 +23,7 @@ export default function (view, params, tabContent) {
SortOrder: 'Ascending', SortOrder: 'Ascending',
IncludeItemTypes: 'Audio', IncludeItemTypes: 'Audio',
Recursive: true, Recursive: true,
Fields: 'AudioInfo,ParentId', Fields: 'ParentId',
StartIndex: 0, StartIndex: 0,
ImageTypeLimit: 1, ImageTypeLimit: 1,
EnableImageTypes: 'Primary' EnableImageTypes: 'Primary'

View file

@ -81,6 +81,10 @@
<span class="material-icons fullscreen" aria-hidden="true"></span> <span class="material-icons fullscreen" aria-hidden="true"></span>
</button> </button>
<button is="paper-icon-button-light" class="btnLyrics autoSize hide" title="${Lyrics}">
<span class="material-icons lyrics" style="top:0.05em" aria-hidden="true"></span>
</button>
<button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${Shuffle}"> <button is="paper-icon-button-light" class="btnShuffleQueue autoSize" title="${Shuffle}">
<span class="material-icons shuffle" aria-hidden="true"></span> <span class="material-icons shuffle" aria-hidden="true"></span>
</button> </button>

View file

@ -1207,7 +1207,6 @@ export default function (view) {
const positionTo = this; const positionTo = this;
import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => { import('../../../components/actionSheet/actionSheet').then(({ default: actionsheet }) => {
mouseWheelVolumeControlDisabled = true; // prevent scrolling through list via mouse wheel to change volume
actionsheet.show({ actionsheet.show({
title: globalize.translate('Chapters'), title: globalize.translate('Chapters'),
items: menuItems, items: menuItems,
@ -1217,7 +1216,6 @@ export default function (view) {
chapterStartPositionTicks => playbackManager.seek(chapterStartPositionTicks, player) chapterStartPositionTicks => playbackManager.seek(chapterStartPositionTicks, player)
).finally(() => { ).finally(() => {
resetIdle(); resetIdle();
mouseWheelVolumeControlDisabled = false;
}); });
setTimeout(resetIdle, 0); setTimeout(resetIdle, 0);
@ -1414,7 +1412,7 @@ export default function (view) {
} }
function onWheel(e) { function onWheel(e) {
if (!mouseWheelVolumeControlDisabled) { if (getOpenedDialog()) return;
if (e.deltaY < 0) { if (e.deltaY < 0) {
playbackManager.volumeUp(currentPlayer); playbackManager.volumeUp(currentPlayer);
} }
@ -1422,7 +1420,6 @@ export default function (view) {
playbackManager.volumeDown(currentPlayer); playbackManager.volumeDown(currentPlayer);
} }
} }
}
function onWindowMouseDown(e) { function onWindowMouseDown(e) {
clickedElement = e.target; clickedElement = e.target;
@ -1616,7 +1613,6 @@ export default function (view) {
let playbackStartTimeTicks = 0; let playbackStartTimeTicks = 0;
let subtitleSyncOverlay; let subtitleSyncOverlay;
let chapterSelectionOptions = []; let chapterSelectionOptions = [];
let mouseWheelVolumeControlDisabled = false;
let trickplayResolution = null; let trickplayResolution = null;
const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider'); const nowPlayingVolumeSlider = view.querySelector('.osdVolumeSlider');
const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer'); const nowPlayingVolumeSliderContainer = view.querySelector('.osdVolumeSliderContainer');

View file

@ -14,6 +14,13 @@
<div class="fieldDescription checkboxFieldDescription">${EnableGamepadHelp}</div> <div class="fieldDescription checkboxFieldDescription">${EnableGamepadHelp}</div>
<div class="fieldDescription checkboxFieldDescription">${LabelPleaseRestart}</div> <div class="fieldDescription checkboxFieldDescription">${LabelPleaseRestart}</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription smoothScrollContainer hide">
<label>
<input type="checkbox" is="emby-checkbox" class="chkSmoothScroll" />
<span>${EnableSmoothScroll}</span>
</label>
</div>
</div> </div>
<button is="emby-button" type="submit" class="raised button-submit block btnSave hide"> <button is="emby-button" type="submit" class="raised button-submit block btnSave hide">

View file

@ -1,3 +1,4 @@
import layoutManager from 'components/layoutManager';
import toast from '../../../components/toast/toast'; import toast from '../../../components/toast/toast';
import globalize from '../../../scripts/globalize'; import globalize from '../../../scripts/globalize';
import appSettings from '../../../scripts/settings/appSettings'; import appSettings from '../../../scripts/settings/appSettings';
@ -6,6 +7,7 @@ import Events from '../../../utils/events.ts';
export default function (view) { export default function (view) {
function submit(e) { function submit(e) {
appSettings.enableGamepad(view.querySelector('.chkEnableGamepad').checked); appSettings.enableGamepad(view.querySelector('.chkEnableGamepad').checked);
appSettings.enableSmoothScroll(view.querySelector('.chkSmoothScroll').checked);
toast(globalize.translate('SettingsSaved')); toast(globalize.translate('SettingsSaved'));
@ -17,7 +19,11 @@ export default function (view) {
} }
view.addEventListener('viewshow', function () { view.addEventListener('viewshow', function () {
view.querySelector('.smoothScrollContainer').classList.toggle('hide', !layoutManager.tv);
view.querySelector('.chkEnableGamepad').checked = appSettings.enableGamepad(); view.querySelector('.chkEnableGamepad').checked = appSettings.enableGamepad();
view.querySelector('.chkSmoothScroll').checked = appSettings.enableSmoothScroll();
view.querySelector('form').addEventListener('submit', submit); view.querySelector('form').addEventListener('submit', submit);
view.querySelector('.btnSave').classList.remove('hide'); view.querySelector('.btnSave').classList.remove('hide');

View file

@ -5,6 +5,7 @@ import profileBuilder from '../../scripts/browserDeviceProfile';
import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings';
import { PluginType } from '../../types/plugin.ts'; import { PluginType } from '../../types/plugin.ts';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import { MediaError } from 'types/mediaError';
function getDefaultProfile() { function getDefaultProfile() {
return profileBuilder({}); return profileBuilder({});
@ -343,7 +344,7 @@ class HtmlAudioPlayer {
return; return;
case 2: case 2:
// MEDIA_ERR_NETWORK // MEDIA_ERR_NETWORK
type = 'network'; type = MediaError.NETWORK_ERROR;
break; break;
case 3: case 3:
// MEDIA_ERR_DECODE // MEDIA_ERR_DECODE
@ -351,12 +352,12 @@ class HtmlAudioPlayer {
htmlMediaHelper.handleHlsJsMediaError(self); htmlMediaHelper.handleHlsJsMediaError(self);
return; return;
} else { } else {
type = 'mediadecodeerror'; type = MediaError.MEDIA_DECODE_ERROR;
} }
break; break;
case 4: case 4:
// MEDIA_ERR_SRC_NOT_SUPPORTED // MEDIA_ERR_SRC_NOT_SUPPORTED
type = 'medianotsupported'; type = MediaError.MEDIA_NOT_SUPPORTED;
break; break;
default: default:
// seeing cases where Edge is firing error events with no error code // seeing cases where Edge is firing error events with no error code

View file

@ -37,6 +37,7 @@ import Events from '../../utils/events.ts';
import { includesAny } from '../../utils/container.ts'; import { includesAny } from '../../utils/container.ts';
import { isHls } from '../../utils/mediaSource.ts'; import { isHls } from '../../utils/mediaSource.ts';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
import { MediaError } from 'types/mediaError';
/** /**
* Returns resolved URL. * Returns resolved URL.
@ -217,7 +218,7 @@ export class HtmlVideoPlayer {
*/ */
#currentAssRenderer; #currentAssRenderer;
/** /**
* @type {null | undefined} * @type {number | undefined}
*/ */
#customTrackIndex; #customTrackIndex;
/** /**
@ -443,6 +444,7 @@ export class HtmlVideoPlayer {
startPosition: options.playerStartPositionTicks / 10000000, startPosition: options.playerStartPositionTicks / 10000000,
manifestLoadingTimeOut: 20000, manifestLoadingTimeOut: 20000,
maxBufferLength: maxBufferLength, maxBufferLength: maxBufferLength,
videoPreference: { preferHDR: true },
xhrSetup(xhr) { xhrSetup(xhr) {
xhr.withCredentials = includeCorsCredentials; xhr.withCredentials = includeCorsCredentials;
} }
@ -519,7 +521,7 @@ export class HtmlVideoPlayer {
if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && isHls(options.mediaSource)) { if (enableHlsJsPlayer(options.mediaSource.RunTimeTicks, 'Video') && isHls(options.mediaSource)) {
return this.setSrcWithHlsJs(elem, options, val); return this.setSrcWithHlsJs(elem, options, val);
} else if (options.playMethod !== 'Transcode' && options.mediaSource.Container === 'flv') { } else if (options.playMethod !== 'Transcode' && options.mediaSource.Container?.toUpperCase() === 'FLV') {
return this.setSrcWithFlvJs(elem, options, val); return this.setSrcWithFlvJs(elem, options, val);
} else { } else {
elem.autoplay = true; elem.autoplay = true;
@ -982,6 +984,8 @@ export class HtmlVideoPlayer {
seekOnPlaybackStart(this, e.target, this._currentPlayOptions.playerStartPositionTicks, () => { seekOnPlaybackStart(this, e.target, this._currentPlayOptions.playerStartPositionTicks, () => {
if (this.#currentAssRenderer) { if (this.#currentAssRenderer) {
this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + this.#currentTrackOffset; this.#currentAssRenderer.timeOffset = (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000 + this.#currentTrackOffset;
this.#currentAssRenderer.resize();
this.#currentAssRenderer.resetRenderAheadCache(false);
} }
}); });
@ -1018,7 +1022,7 @@ export class HtmlVideoPlayer {
// Only trigger this if there is media info // Only trigger this if there is media info
// Avoid triggering in situations where it might not actually have a video stream (audio only live tv channel) // Avoid triggering in situations where it might not actually have a video stream (audio only live tv channel)
if (!mediaSource || mediaSource.RunTimeTicks) { if (!mediaSource || mediaSource.RunTimeTicks) {
onErrorInternal(this, 'mediadecodeerror'); onErrorInternal(this, MediaError.NO_MEDIA_ERROR);
} }
} }
} }
@ -1070,7 +1074,7 @@ export class HtmlVideoPlayer {
return; return;
case 2: case 2:
// MEDIA_ERR_NETWORK // MEDIA_ERR_NETWORK
type = 'network'; type = MediaError.NETWORK_ERROR;
break; break;
case 3: case 3:
// MEDIA_ERR_DECODE // MEDIA_ERR_DECODE
@ -1078,12 +1082,12 @@ export class HtmlVideoPlayer {
handleHlsJsMediaError(this); handleHlsJsMediaError(this);
return; return;
} else { } else {
type = 'mediadecodeerror'; type = MediaError.MEDIA_DECODE_ERROR;
} }
break; break;
case 4: case 4:
// MEDIA_ERR_SRC_NOT_SUPPORTED // MEDIA_ERR_SRC_NOT_SUPPORTED
type = 'medianotsupported'; type = MediaError.MEDIA_NOT_SUPPORTED;
break; break;
default: default:
// seeing cases where Edge is firing error events with no error code // seeing cases where Edge is firing error events with no error code
@ -1168,9 +1172,9 @@ export class HtmlVideoPlayer {
this.#currentClock = null; this.#currentClock = null;
this._currentAspectRatio = null; this._currentAspectRatio = null;
const jassub = this.#currentAssRenderer; const octopus = this.#currentAssRenderer;
if (jassub) { if (octopus) {
jassub.destroy(); octopus.dispose();
} }
this.#currentAssRenderer = null; this.#currentAssRenderer = null;
} }
@ -1259,43 +1263,36 @@ export class HtmlVideoPlayer {
const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', { const fallbackFontList = apiClient.getUrl('/FallbackFont/Fonts', {
api_key: apiClient.accessToken() api_key: apiClient.accessToken()
}); });
// TODO: replace with `event-target-polyfill` once https://github.com/benlesh/event-target-polyfill/pull/12 or 11 is merged const htmlVideoPlayer = this;
import('event-target-polyfill').then(() => { import('@jellyfin/libass-wasm').then(({ default: SubtitlesOctopus }) => {
import('jassub').then(({ default: JASSUB }) => {
// test SIMD support
JASSUB._test();
const options = { const options = {
video: videoElement, video: videoElement,
subUrl: getTextTrackUrl(track, item), subUrl: getTextTrackUrl(track, item),
fonts: avaliableFonts, fonts: avaliableFonts,
fallbackFont: 'liberation sans', workerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker.js`,
availableFonts: { 'liberation sans': `${appRouter.baseUrl()}/default.woff2` }, legacyWorkerUrl: `${appRouter.baseUrl()}/libraries/subtitles-octopus-worker-legacy.js`,
// Disabled eslint compat, but is safe as corejs3 polyfills URL onError() {
// eslint-disable-next-line compat/compat // HACK: Clear JavascriptSubtitlesOctopus: it gets disposed when an error occurs
workerUrl: new URL('jassub/dist/jassub-worker.js', import.meta.url).href, htmlVideoPlayer.#currentAssRenderer = null;
// eslint-disable-next-line compat/compat
wasmUrl: new URL('jassub/dist/jassub-worker.wasm', import.meta.url).href, // HACK: Give JavascriptSubtitlesOctopus time to dispose itself
// eslint-disable-next-line compat/compat setTimeout(() => {
legacyWasmUrl: new URL('jassub/dist/jassub-worker.wasm.js', import.meta.url).href, onErrorInternal(this, MediaError.ASS_RENDER_ERROR);
// eslint-disable-next-line compat/compat }, 0);
modernWasmUrl : new URL('jassub/dist/jassub-worker-modern.wasm', import.meta.url).href, },
timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000, timeOffset: (this._currentPlayOptions.transcodingOffsetTicks || 0) / 10000000,
// new jassub options; override all, even defaults
blendMode: 'js', // new octopus options; override all, even defaults
asyncRender: true, renderMode: 'wasm-blend',
offscreenRender: true,
// RVFC is polyfilled everywhere, but webOS 2 reports polyfill API's as functional even tho they aren't
onDemandRender: browser.web0sVersion !== 2,
useLocalFonts: true,
dropAllAnimations: false, dropAllAnimations: false,
dropAllBlur: !JASSUB._supportsSIMD,
libassMemoryLimit: 40, libassMemoryLimit: 40,
libassGlyphLimit: 40, libassGlyphLimit: 40,
targetFps: 24, targetFps: 24,
prescaleFactor: 0.8, prescaleFactor: 0.8,
prescaleHeightLimit: 1080, prescaleHeightLimit: 1080,
maxRenderHeight: 2160 maxRenderHeight: 2160,
resizeVariation: 0.2,
renderAhead: 90
}; };
Promise.all([ Promise.all([
@ -1307,12 +1304,6 @@ export class HtmlVideoPlayer {
options.workerUrl = workerUrl; options.workerUrl = workerUrl;
options.legacyWorkerUrl = legacyWorkerUrl; options.legacyWorkerUrl = legacyWorkerUrl;
const cleanup = () => {
this.#currentAssRenderer.destroy();
this.#currentAssRenderer = null;
onErrorInternal(this, 'mediadecodeerror');
};
if (config.EnableFallbackFont) { if (config.EnableFallbackFont) {
apiClient.getJSON(fallbackFontList).then((fontFiles = []) => { apiClient.getJSON(fallbackFontList).then((fontFiles = []) => {
fontFiles.forEach(font => { fontFiles.forEach(font => {
@ -1321,16 +1312,13 @@ export class HtmlVideoPlayer {
}); });
avaliableFonts.push(fontUrl); avaliableFonts.push(fontUrl);
}); });
this.#currentAssRenderer = new JASSUB(options); this.#currentAssRenderer = new SubtitlesOctopus(options);
this.#currentAssRenderer.addEventListener('error', cleanup, { once: true });
}); });
} else { } else {
this.#currentAssRenderer = new JASSUB(options); this.#currentAssRenderer = new SubtitlesOctopus(options);
this.#currentAssRenderer.addEventListener('error', cleanup, { once: true });
} }
}); });
}); });
});
} }
/** /**

View file

@ -17,7 +17,7 @@
z-index: 1000; z-index: 1000;
} }
.videoPlayerContainer .JASSUB { .videoPlayerContainer .libassjs-canvas-parent {
order: -1; order: -1;
} }

View file

@ -1,6 +1,6 @@
import browser from './browser';
import appSettings from './settings/appSettings'; import appSettings from './settings/appSettings';
import * as userSettings from './settings/userSettings'; import * as userSettings from './settings/userSettings';
import browser from './browser';
function canPlayH264(videoTestElement) { function canPlayH264(videoTestElement) {
return !!(videoTestElement.canPlayType?.('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, '')); return !!(videoTestElement.canPlayType?.('video/mp4; codecs="avc1.42E01E, mp4a.40.2"').replace(/no/, ''));
@ -210,12 +210,22 @@ function supportsDolbyVision(options) {
); );
} }
function canPlayDolbyVisionHevc(videoTestElement) { function supportedDolbyVisionProfilesHevc(videoTestElement) {
// Profiles 5/7/8 4k@60fps const supportedProfiles = [];
return !!videoTestElement.canPlayType // Profiles 5/8 4k@60fps
&& (videoTestElement.canPlayType('video/mp4; codecs="dvh1.05.09"').replace(/no/, '') if (videoTestElement.canPlayType) {
&& videoTestElement.canPlayType('video/mp4; codecs="dvh1.07.09"').replace(/no/, '') if (videoTestElement
&& videoTestElement.canPlayType('video/mp4; codecs="dvh1.08.09"').replace(/no/, '')); .canPlayType('video/mp4; codecs="dvh1.05.09"')
.replace(/no/, '')) {
supportedProfiles.push(5);
}
if (videoTestElement
.canPlayType('video/mp4; codecs="dvh1.08.09"')
.replace(/no/, '')) {
supportedProfiles.push(8);
}
}
return supportedProfiles;
} }
function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) { function getDirectPlayProfileForVideoContainer(container, videoAudioCodecs, videoTestElement, options) {
@ -706,11 +716,14 @@ export default function (options) {
profile.TranscodingProfiles = []; profile.TranscodingProfiles = [];
const hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls(); const hlsBreakOnNonKeyFrames = browser.iOS || browser.osx || browser.edge || !canPlayNativeHls();
let enableFmp4Hls = userSettings.preferFmp4HlsContainer();
if ((browser.safari || browser.tizen || browser.web0s) && !canPlayNativeHlsInFmp4()) {
enableFmp4Hls = false;
}
if (canPlayHls() && browser.enableHlsAudio !== false) { if (canPlayHls() && browser.enableHlsAudio !== false) {
profile.TranscodingProfiles.push({ profile.TranscodingProfiles.push({
// hlsjs, edge, and android all seem to require ts container Container: enableFmp4Hls ? 'mp4' : 'ts',
Container: !canPlayNativeHls() || browser.edge || browser.android ? 'ts' : 'aac',
Type: 'Audio', Type: 'Audio',
AudioCodec: 'aac', AudioCodec: 'aac',
Context: 'Streaming', Context: 'Streaming',
@ -747,10 +760,6 @@ export default function (options) {
}); });
if (canPlayHls() && options.enableHls !== false) { if (canPlayHls() && options.enableHls !== false) {
let enableFmp4Hls = userSettings.preferFmp4HlsContainer();
if ((browser.safari || browser.tizen || browser.web0s) && !canPlayNativeHlsInFmp4()) {
enableFmp4Hls = false;
}
if (hlsInFmp4VideoCodecs.length && hlsInFmp4VideoAudioCodecs.length && enableFmp4Hls) { if (hlsInFmp4VideoCodecs.length && hlsInFmp4VideoAudioCodecs.length && enableFmp4Hls) {
// HACK: Since there is no filter for TS/MP4 in the API, specify HLS support in general and rely on retry after DirectPlay error // HACK: Since there is no filter for TS/MP4 in the API, specify HLS support in general and rely on retry after DirectPlay error
// FIXME: Need support for {Container: 'mp4', Protocol: 'hls'} or {Container: 'hls', SubContainer: 'mp4'} // FIXME: Need support for {Container: 'mp4', Protocol: 'hls'} or {Container: 'hls', SubContainer: 'mp4'}
@ -942,9 +951,15 @@ export default function (options) {
av1VideoRangeTypes += '|HLG'; av1VideoRangeTypes += '|HLG';
} }
if (supportsDolbyVision(options) && canPlayDolbyVisionHevc(videoTestElement)) { if (supportsDolbyVision(options)) {
const profiles = supportedDolbyVisionProfilesHevc(videoTestElement);
if (profiles.includes(5)) {
hevcVideoRangeTypes += '|DOVI'; hevcVideoRangeTypes += '|DOVI';
} }
if (profiles.includes(8)) {
hevcVideoRangeTypes += '|DOVIWithHDR10|DOVIWithHLG|DOVIWithSDR';
}
}
const h264CodecProfileConditions = [ const h264CodecProfileConditions = [
{ {
@ -1131,7 +1146,7 @@ export default function (options) {
// On iOS 12.x, for TS container max h264 level is 4.2 // On iOS 12.x, for TS container max h264 level is 4.2
if (browser.iOS && browser.iOSVersion < 13) { if (browser.iOS && browser.iOSVersion < 13) {
const codecProfile = { const codecProfileTS = {
Type: 'Video', Type: 'Video',
Codec: 'h264', Codec: 'h264',
Container: 'ts', Container: 'ts',
@ -1140,14 +1155,32 @@ export default function (options) {
}) })
}; };
codecProfile.Conditions.push({ codecProfileTS.Conditions.push({
Condition: 'LessThanEqual', Condition: 'LessThanEqual',
Property: 'VideoLevel', Property: 'VideoLevel',
Value: '42', Value: '42',
IsRequired: false IsRequired: false
}); });
profile.CodecProfiles.push(codecProfile); profile.CodecProfiles.push(codecProfileTS);
const codecProfileMp4 = {
Type: 'Video',
Codec: 'h264',
Container: 'mp4',
Conditions: h264CodecProfileConditions.filter((condition) => {
return condition.Property !== 'VideoLevel';
})
};
codecProfileMp4.Conditions.push({
Condition: 'LessThanEqual',
Property: 'VideoLevel',
Value: '42',
IsRequired: false
});
profile.CodecProfiles.push(codecProfileMp4);
} }
profile.CodecProfiles.push({ profile.CodecProfiles.push({
@ -1250,4 +1283,3 @@ export default function (options) {
return profile; return profile;
} }

View file

@ -1,28 +1,41 @@
import globalize from './globalize';
import alert from '../components/alert';
import confirm from '../components/confirm/confirm'; import confirm from '../components/confirm/confirm';
import { appRouter } from '../components/router/appRouter'; import { appRouter } from '../components/router/appRouter';
import globalize from './globalize';
import ServerConnections from '../components/ServerConnections'; import ServerConnections from '../components/ServerConnections';
import alert from '../components/alert'; import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
function alertText(options) { function alertText(options) {
return alert(options); return alert(options);
} }
function getDeletionConfirmContent(item) {
if (item.Type === BaseItemKind.Series) {
const totalEpisodes = item.RecursiveItemCount;
return {
title: globalize.translate('HeaderDeleteSeries'),
text: globalize.translate('ConfirmDeleteSeries', totalEpisodes),
confirmText: globalize.translate('DeleteEntireSeries', totalEpisodes),
primary: 'delete'
};
}
return {
title: globalize.translate('HeaderDeleteItem'),
text: globalize.translate('ConfirmDeleteItem'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
};
}
export function deleteItem(options) { export function deleteItem(options) {
const item = options.item; const item = options.item;
const parentId = item.SeasonId || item.SeriesId || item.ParentId; const parentId = item.SeasonId || item.SeriesId || item.ParentId;
const apiClient = ServerConnections.getApiClient(item.ServerId); const apiClient = ServerConnections.getApiClient(item.ServerId);
return confirm({ return confirm(getDeletionConfirmContent(item)).then(function () {
title: globalize.translate('HeaderDeleteItem'),
text: globalize.translate('ConfirmDeleteItem'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(function () {
return apiClient.deleteItem(item.Id).then(function () { return apiClient.deleteItem(item.Id).then(function () {
if (options.navigate) { if (options.navigate) {
if (parentId) { if (parentId) {
@ -41,6 +54,28 @@ export function deleteItem(options) {
}); });
} }
export function deleteLyrics (item) {
return confirm({
title: globalize.translate('HeaderDeleteLyrics'),
text: globalize.translate('ConfirmDeleteLyrics'),
confirmText: globalize.translate('Delete'),
primary: 'delete'
}).then(() => {
const apiClient = ServerConnections.getApiClient(item.ServerId);
return apiClient.ajax({
url: apiClient.getUrl('Audio/' + item.Id + '/Lyrics'),
type: 'DELETE'
}).catch((err) => {
const result = function () {
return Promise.reject(err);
};
return alertText(globalize.translate('ErrorDeletingLyrics')).then(result, result);
});
});
}
export default { export default {
deleteItem: deleteItem deleteItem,
deleteLyrics
}; };

View file

@ -343,7 +343,7 @@ function getQuery(options, item) {
SortOrder: 'Ascending', SortOrder: 'Ascending',
IncludeItemTypes: '', IncludeItemTypes: '',
Recursive: true, Recursive: true,
Fields: 'AudioInfo,ParentId,PrimaryImageAspectRatio', Fields: 'ParentId,PrimaryImageAspectRatio',
Limit: 100, Limit: 100,
StartIndex: 0, StartIndex: 0,
CollapseBoxSetItems: false CollapseBoxSetItems: false

View file

@ -1,203 +0,0 @@
import loading from '../components/loading/loading';
import listView from '../components/listview/listview';
import cardBuilder from '../components/cardbuilder/cardBuilder';
import libraryMenu from './libraryMenu';
import libraryBrowser from './libraryBrowser';
import imageLoader from '../components/images/imageLoader';
import * as userSettings from './settings/userSettings';
import '../elements/emby-itemscontainer/emby-itemscontainer';
import Dashboard from '../utils/dashboard';
export default function (view) {
function getPageData() {
const key = getSavedQueryKey();
let pageData = data[key];
if (!pageData) {
pageData = data[key] = {
query: {
SortBy: 'SortName',
SortOrder: 'Ascending',
IncludeItemTypes: 'Playlist',
Recursive: true,
Fields: 'PrimaryImageAspectRatio,SortName,CumulativeRunTimeTicks,CanDelete',
StartIndex: 0
},
view: userSettings.getSavedView(key) || 'Poster'
};
if (userSettings.libraryPageSize() > 0) {
pageData.query['Limit'] = userSettings.libraryPageSize();
}
pageData.query.ParentId = libraryMenu.getTopParentId();
userSettings.loadQuerySettings(key, pageData.query);
}
return pageData;
}
function getQuery() {
return getPageData().query;
}
function getSavedQueryKey() {
return `${libraryMenu.getTopParentId()}-playlists`;
}
function showLoadingMessage() {
loading.show();
}
function hideLoadingMessage() {
loading.hide();
}
function onViewStyleChange() {
const viewStyle = getPageData().view;
const itemsContainer = view.querySelector('.itemsContainer');
if (viewStyle == 'List') {
itemsContainer.classList.add('vertical-list');
itemsContainer.classList.remove('vertical-wrap');
} else {
itemsContainer.classList.remove('vertical-list');
itemsContainer.classList.add('vertical-wrap');
}
itemsContainer.innerHTML = '';
}
function reloadItems() {
showLoadingMessage();
const query = getQuery();
const promise1 = ApiClient.getItems(Dashboard.getCurrentUserId(), query);
// TODO: promise2 is unused, check if necessary.
const promise2 = Dashboard.getCurrentUser();
Promise.all([promise1, promise2]).then(function (responses) {
const result = responses[0];
// TODO: Is the scroll necessary?
window.scrollTo(0, 0);
let html = '';
const viewStyle = getPageData().view;
view.querySelector('.listTopPaging').innerHTML = libraryBrowser.getQueryPagingHtml({
startIndex: query.StartIndex,
limit: query.Limit,
totalRecordCount: result.TotalRecordCount,
viewButton: false,
showLimit: false,
updatePageSizeSetting: false,
addLayoutButton: true,
layouts: 'List,Poster,PosterCard,Thumb,ThumbCard',
currentLayout: viewStyle
});
if (result.TotalRecordCount) {
if (viewStyle == 'List') {
html = listView.getListViewHtml({
items: result.Items,
sortBy: query.SortBy
});
} else if (viewStyle == 'PosterCard') {
html = cardBuilder.getCardsHtml({
items: result.Items,
shape: 'square',
coverImage: true,
showTitle: true,
cardLayout: true
});
} else if (viewStyle == 'Thumb') {
html = cardBuilder.getCardsHtml({
items: result.Items,
shape: 'backdrop',
showTitle: true,
centerText: true,
preferThumb: true,
overlayPlayButton: true
});
} else if (viewStyle == 'ThumbCard') {
html = cardBuilder.getCardsHtml({
items: result.Items,
shape: 'backdrop',
showTitle: true,
preferThumb: true,
cardLayout: true
});
} else {
html = cardBuilder.getCardsHtml({
items: result.Items,
shape: 'square',
showTitle: true,
coverImage: true,
centerText: true,
overlayPlayButton: true
});
}
view.querySelector('.noItemsMessage').classList.add('hide');
} else {
view.querySelector('.noItemsMessage').classList.remove('hide');
}
const elem = view.querySelector('.itemsContainer');
elem.innerHTML = html;
imageLoader.lazyChildren(elem);
const btnNextPage = view.querySelector('.btnNextPage');
if (btnNextPage) {
btnNextPage.addEventListener('click', function () {
if (userSettings.libraryPageSize() > 0) {
query.StartIndex += query.Limit;
}
reloadItems();
});
}
const btnPreviousPage = view.querySelector('.btnPreviousPage');
if (btnPreviousPage) {
btnPreviousPage.addEventListener('click', function () {
if (userSettings.libraryPageSize() > 0) {
query.StartIndex = Math.max(0, query.StartIndex - query.Limit);
}
reloadItems();
});
}
const btnChangeLayout = view.querySelector('.btnChangeLayout');
if (btnChangeLayout) {
btnChangeLayout.addEventListener('layoutchange', function (e) {
const layout = e.detail.viewStyle;
getPageData().view = layout;
userSettings.saveViewSetting(getSavedQueryKey(), layout);
onViewStyleChange();
reloadItems();
});
}
userSettings.saveQuerySettings(getSavedQueryKey(), query);
hideLoadingMessage();
});
}
const data = {};
view.addEventListener('viewbeforeshow', function () {
reloadItems();
});
view.querySelector('.btnNewPlaylist').addEventListener('click', function () {
import('../components/playlisteditor/playlisteditor').then(({ default: PlaylistEditor }) => {
const serverId = ApiClient.serverInfo().Id;
const playlistEditor = new PlaylistEditor();
playlistEditor.show({
items: [],
serverId: serverId
}).catch(() => {
// Dialog closed
});
}).catch(err => {
console.error('[btnNewPlaylist] failed to load playlist editor', err);
});
});
onViewStyleChange();
}

View file

@ -1,3 +1,4 @@
import browser from 'scripts/browser';
import Events from '../../utils/events.ts'; import Events from '../../utils/events.ts';
import { toBoolean } from '../../utils/string.ts'; import { toBoolean } from '../../utils/string.ts';
@ -31,6 +32,19 @@ class AppSettings {
return toBoolean(this.get('enableGamepad'), false); return toBoolean(this.get('enableGamepad'), false);
} }
/**
* Get or set 'Enable smooth scroll' state.
* @param {boolean|undefined} val - Flag to enable 'Enable smooth scroll' or undefined.
* @return {boolean} 'Enable smooth scroll' state.
*/
enableSmoothScroll(val) {
if (val !== undefined) {
return this.set('enableSmoothScroll', val.toString());
}
return toBoolean(this.get('enableSmoothScroll'), !!browser.tizen);
}
enableSystemExternalPlayers(val) { enableSystemExternalPlayers(val) {
if (val !== undefined) { if (val !== undefined) {
this.set('enableSystemExternalPlayers', val.toString()); this.set('enableSystemExternalPlayers', val.toString());
@ -105,6 +119,19 @@ class AppSettings {
return parseInt(this.get('maxVideoWidth') || '0', 10) || 0; return parseInt(this.get('maxVideoWidth') || '0', 10) || 0;
} }
/**
* Get or set 'Limit maximum supported video resolution' state.
* @param {boolean|undefined} val - Flag to enable 'Limit maximum supported video resolution' or undefined.
* @return {boolean} 'Limit maximum supported video resolution' state.
*/
limitSupportedVideoResolution(val) {
if (val !== undefined) {
return this.set('limitSupportedVideoResolution', val.toString());
}
return toBoolean(this.get('limitSupportedVideoResolution'), false);
}
set(name, value, userId) { set(name, value, userId) {
const currentValue = this.get(name, userId); const currentValue = this.get(name, userId);
localStorage.setItem(this.#getKey(name, userId), value); localStorage.setItem(this.#getKey(name, userId), value);

View file

@ -68,7 +68,7 @@ export class UserSettings {
* Set value of setting. * Set value of setting.
* @param {string} name - Name of setting. * @param {string} name - Name of setting.
* @param {mixed} value - Value of setting. * @param {mixed} value - Value of setting.
* @param {boolean} enableOnServer - Flag to save preferences on server. * @param {boolean} [enableOnServer] - Flag to save preferences on server.
*/ */
set(name, value, enableOnServer) { set(name, value, enableOnServer) {
const userId = this.currentUserId; const userId = this.currentUserId;
@ -90,7 +90,7 @@ export class UserSettings {
/** /**
* Get value of setting. * Get value of setting.
* @param {string} name - Name of setting. * @param {string} name - Name of setting.
* @param {boolean} enableOnServer - Flag to return preferences from server (cached). * @param {boolean} [enableOnServer] - Flag to return preferences from server (cached).
* @return {string} Value of setting. * @return {string} Value of setting.
*/ */
get(name, enableOnServer) { get(name, enableOnServer) {
@ -199,7 +199,7 @@ export class UserSettings {
/** /**
* Get or set 'Theme Songs' state. * Get or set 'Theme Songs' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Songs' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Theme Songs' or undefined.
* @return {boolean} 'Theme Songs' state. * @return {boolean} 'Theme Songs' state.
*/ */
enableThemeSongs(val) { enableThemeSongs(val) {
@ -212,7 +212,7 @@ export class UserSettings {
/** /**
* Get or set 'Theme Videos' state. * Get or set 'Theme Videos' state.
* @param {boolean|undefined} val - Flag to enable 'Theme Videos' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Theme Videos' or undefined.
* @return {boolean} 'Theme Videos' state. * @return {boolean} 'Theme Videos' state.
*/ */
enableThemeVideos(val) { enableThemeVideos(val) {
@ -225,7 +225,7 @@ export class UserSettings {
/** /**
* Get or set 'Fast Fade-in' state. * Get or set 'Fast Fade-in' state.
* @param {boolean|undefined} val - Flag to enable 'Fast Fade-in' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Fast Fade-in' or undefined.
* @return {boolean} 'Fast Fade-in' state. * @return {boolean} 'Fast Fade-in' state.
*/ */
enableFastFadein(val) { enableFastFadein(val) {
@ -238,7 +238,7 @@ export class UserSettings {
/** /**
* Get or set 'Blurhash' state. * Get or set 'Blurhash' state.
* @param {boolean|undefined} val - Flag to enable 'Blurhash' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Blurhash' or undefined.
* @return {boolean} 'Blurhash' state. * @return {boolean} 'Blurhash' state.
*/ */
enableBlurhash(val) { enableBlurhash(val) {
@ -251,7 +251,7 @@ export class UserSettings {
/** /**
* Get or set 'Backdrops' state. * Get or set 'Backdrops' state.
* @param {boolean|undefined} val - Flag to enable 'Backdrops' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Backdrops' or undefined.
* @return {boolean} 'Backdrops' state. * @return {boolean} 'Backdrops' state.
*/ */
enableBackdrops(val) { enableBackdrops(val) {
@ -264,7 +264,7 @@ export class UserSettings {
/** /**
* Get or set 'disableCustomCss' state. * Get or set 'disableCustomCss' state.
* @param {boolean|undefined} val - Flag to enable 'disableCustomCss' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'disableCustomCss' or undefined.
* @return {boolean} 'disableCustomCss' state. * @return {boolean} 'disableCustomCss' state.
*/ */
disableCustomCss(val) { disableCustomCss(val) {
@ -277,7 +277,7 @@ export class UserSettings {
/** /**
* Get or set customCss. * Get or set customCss.
* @param {string|undefined} val - Language. * @param {string|undefined} [val] - Language.
* @return {string} Language. * @return {string} Language.
*/ */
customCss(val) { customCss(val) {
@ -290,7 +290,7 @@ export class UserSettings {
/** /**
* Get or set 'Details Banner' state. * Get or set 'Details Banner' state.
* @param {boolean|undefined} val - Flag to enable 'Details Banner' or undefined. * @param {boolean|undefined} [val] - Flag to enable 'Details Banner' or undefined.
* @return {boolean} 'Details Banner' state. * @return {boolean} 'Details Banner' state.
*/ */
detailsBanner(val) { detailsBanner(val) {
@ -316,7 +316,7 @@ export class UserSettings {
/** /**
* Get or set language. * Get or set language.
* @param {string|undefined} val - Language. * @param {string|undefined} [val] - Language.
* @return {string} Language. * @return {string} Language.
*/ */
language(val) { language(val) {
@ -329,7 +329,7 @@ export class UserSettings {
/** /**
* Get or set datetime locale. * Get or set datetime locale.
* @param {string|undefined} val - Datetime locale. * @param {string|undefined} [val] - Datetime locale.
* @return {string} Datetime locale. * @return {string} Datetime locale.
*/ */
dateTimeLocale(val) { dateTimeLocale(val) {
@ -368,7 +368,7 @@ export class UserSettings {
/** /**
* Get or set theme for Dashboard. * Get or set theme for Dashboard.
* @param {string|undefined} val - Theme for Dashboard. * @param {string|undefined} [val] - Theme for Dashboard.
* @return {string} Theme for Dashboard. * @return {string} Theme for Dashboard.
*/ */
dashboardTheme(val) { dashboardTheme(val) {
@ -394,7 +394,7 @@ export class UserSettings {
/** /**
* Get or set main theme. * Get or set main theme.
* @param {string|undefined} val - Main theme. * @param {string|undefined} [val] - Main theme.
* @return {string} Main theme. * @return {string} Main theme.
*/ */
theme(val) { theme(val) {
@ -407,7 +407,7 @@ export class UserSettings {
/** /**
* Get or set screensaver. * Get or set screensaver.
* @param {string|undefined} val - Screensaver. * @param {string|undefined} [val] - Screensaver.
* @return {string} Screensaver. * @return {string} Screensaver.
*/ */
screensaver(val) { screensaver(val) {
@ -420,7 +420,7 @@ export class UserSettings {
/** /**
* Get or set the interval between backdrops when using the backdrop screensaver. * Get or set the interval between backdrops when using the backdrop screensaver.
* @param {number|undefined} val - The interval between backdrops in seconds. * @param {number|undefined} [val] - The interval between backdrops in seconds.
* @return {number} The interval between backdrops in seconds. * @return {number} The interval between backdrops in seconds.
*/ */
backdropScreensaverInterval(val) { backdropScreensaverInterval(val) {
@ -433,7 +433,7 @@ export class UserSettings {
/** /**
* Get or set library page size. * Get or set library page size.
* @param {number|undefined} val - Library page size. * @param {number|undefined} [val] - Library page size.
* @return {number} Library page size. * @return {number} Library page size.
*/ */
libraryPageSize(val) { libraryPageSize(val) {

View file

@ -52,7 +52,7 @@
"LabelUser": "Карыстальнік", "LabelUser": "Карыстальнік",
"Trailer": "Трэйлер", "Trailer": "Трэйлер",
"BehindTheScenes": "За кулісамі", "BehindTheScenes": "За кулісамі",
"EnableEnhancedNvdecDecoderHelp": "Эксперыментальная рэалізацыя NVDEC, не ўключайце гэту опцыю, калі вы не сутыкнуліся з памылкамі дэкадавання.", "EnableEnhancedNvdecDecoderHelp": "Палепшаная рэалізацыя NVDEC, адключыце гэту опцыю, каб выкарыстоўваць CUVID, калі вы сутыкнуліся з памылкамі дэкадавання.",
"LabelYear": "Год", "LabelYear": "Год",
"LatestFromLibrary": "Нядаўна дададзеныя ў {0}", "LatestFromLibrary": "Нядаўна дададзеныя ў {0}",
"Lyricist": "Аўтар тэкстаў", "Lyricist": "Аўтар тэкстаў",
@ -72,7 +72,7 @@
"AllowFfmpegThrottlingHelp": "Калі перакадзіраванне або рэмукс будзе дастаткова далёка ад бягучай пазіцыі прайгравання, прыпыніце працэс, каб ён спажываў менш рэсурсаў. Гэта найбольш карысна пры праглядзе без частага пошуку. Выключыце гэта, калі ўзнікнуць праблемы з прайграваннем.", "AllowFfmpegThrottlingHelp": "Калі перакадзіраванне або рэмукс будзе дастаткова далёка ад бягучай пазіцыі прайгравання, прыпыніце працэс, каб ён спажываў менш рэсурсаў. Гэта найбольш карысна пры праглядзе без частага пошуку. Выключыце гэта, калі ўзнікнуць праблемы з прайграваннем.",
"AllowOnTheFlySubtitleExtractionHelp": "Убудаваныя субтытры можна атрымаць з відэа і даставіць кліентам у выглядзе звычайнага тэксту, каб прадухіліць перакадзіраванне відэа. У некаторых сістэмах гэта можа заняць шмат часу і прывесці да спынення прайгравання відэа падчас працэсу здабывання. Адключыце гэта, каб убудаваныя субтытры запісваліся пры перакадзіраванні відэа, калі яны не падтрымліваюцца кліенцкай прыладай.", "AllowOnTheFlySubtitleExtractionHelp": "Убудаваныя субтытры можна атрымаць з відэа і даставіць кліентам у выглядзе звычайнага тэксту, каб прадухіліць перакадзіраванне відэа. У некаторых сістэмах гэта можа заняць шмат часу і прывесці да спынення прайгравання відэа падчас працэсу здабывання. Адключыце гэта, каб убудаваныя субтытры запісваліся пры перакадзіраванні відэа, калі яны не падтрымліваюцца кліенцкай прыладай.",
"AllowRemoteAccessHelp": "Калі не пазначыць, усе аддаленыя злучэнні будуць заблакіраваны.", "AllowRemoteAccessHelp": "Калі не пазначыць, усе аддаленыя злучэнні будуць заблакіраваны.",
"AllowTonemappingHelp": "Адлюстраванне тонаў можа пераўтварыць дынамічны дыяпазон відэа з HDR у SDR, захоўваючы дэталі выявы і колеры, якія з'яўляюцца вельмі важнай інфармацыяй для прадстаўлення арыгінальнай сцэны. У цяперашні час працуе толькі з відэа 10bit HDR10, HLG і DoVi. Для гэтага патрабуецца адпаведнае асяроддзе выканання OpenCL або CUDA.", "AllowTonemappingHelp": "Адлюстраванне тонаў можа пераўтварыць дынамічны дыяпазон відэа з HDR у SDR, захоўваючы дэталі выявы і колеры, якія з'яўляюцца вельмі важнай інфармацыяй для прадстаўлення арыгінальнай сцэны. У цяперашні час працуе толькі з відэа 10bit HDR10, HLG і DoVi. Для гэтага патрабуецца адпаведнае асяроддзе выканання GPGPU.",
"AlwaysPlaySubtitles": "Заўсёды граць", "AlwaysPlaySubtitles": "Заўсёды граць",
"ApiKeysCaption": "Спіс уключаных на дадзены момант ключоў API", "ApiKeysCaption": "Спіс уключаных на дадзены момант ключоў API",
"AroundTime": "Каля {0}", "AroundTime": "Каля {0}",
@ -576,7 +576,7 @@
"LabelPlaybackInfo": "Інфармацыя аб прайграванні", "LabelPlaybackInfo": "Інфармацыя аб прайграванні",
"LabelVideoInfo": "Інфармацыя пра відэа", "LabelVideoInfo": "Інфармацыя пра відэа",
"PreferFmp4HlsContainer": "Аддайце перавагу медыякантэйнеру fMP4-HLS", "PreferFmp4HlsContainer": "Аддайце перавагу медыякантэйнеру fMP4-HLS",
"PreferFmp4HlsContainerHelp": "Аддавайце перавагу выкарыстоўванню fMP4 у якасці кантэйнера па змаўчанні для HLS, што дазваляе накіроўваць струменевае змесціва HEVC на прылады, якія падтрымліваюцца.", "PreferFmp4HlsContainerHelp": "Аддавайце перавагу выкарыстоўванню fMP4 у якасці кантэйнера па змаўчанні для HLS, што дае магчымасць накіраваць струменевае змесціва HEVC і AV1 на прылады, якія падтрымліваюцца.",
"AllowHevcEncoding": "Дазволіць кадзіраванне ў фармаце HEVC", "AllowHevcEncoding": "Дазволіць кадзіраванне ў фармаце HEVC",
"LabelSelectAudioChannels": "Каналы", "LabelSelectAudioChannels": "Каналы",
"YoutubePlaybackError": "Запытанае відэа немагчыма прайграць.", "YoutubePlaybackError": "Запытанае відэа немагчыма прайграць.",
@ -586,7 +586,7 @@
"EnableVppTonemapping": "Уключыць VPP Tone mapping", "EnableVppTonemapping": "Уключыць VPP Tone mapping",
"LabelEnableGamepad": "Уключыць геймпад", "LabelEnableGamepad": "Уключыць геймпад",
"VideoCodecNotSupported": "Відэакодэк не падтрымліваецца", "VideoCodecNotSupported": "Відэакодэк не падтрымліваецца",
"H264CrfHelp": "\"Каэфіцыент пастаяннай хуткасці\" (CRF) - гэта налада якасці па змаўчанні для кадавальніка x264 і x265. Вы можаце ўсталяваць значэнні ад 0 да 51, дзе больш нізкія значэнні прывядуць да лепшай якасці (за кошт большага памеру файла). Разумныя значэнні знаходзяцца паміж 18 і 28. Значэнне па змаўчанні для x264 роўна 23, а для x265 - 28, так што вы можаце выкарыстоўваць гэта як адпраўную кропку.", "H264CrfHelp": "\"Каэфіцыент пастаяннай хуткасці\" (CRF) - гэта налада якасці па змаўчанні для праграмных кадавальнікаў x264 і x265. Вы можаце ўсталяваць значэнні ад 0 да 51, дзе больш нізкія значэнні прывядуць да лепшай якасці (за кошт большага памеру файла). Разумныя значэнні знаходзяцца паміж 18 і 28. Значэнне па змаўчанні для x264 роўна 23, а для x265 - 28, так што вы можаце выкарыстоўваць гэта як адпраўную кропку. Апаратныя кадавальнікі не выкарыстоўваюць гэтыя параметры.",
"HDPrograms": "HD-праграмы", "HDPrograms": "HD-праграмы",
"AnamorphicVideoNotSupported": "Анаморфнае відэа не падтрымліваецца", "AnamorphicVideoNotSupported": "Анаморфнае відэа не падтрымліваецца",
"SecondaryAudioNotSupported": "Другасныя гукавыя дарожкі не падтрымліваюцца", "SecondaryAudioNotSupported": "Другасныя гукавыя дарожкі не падтрымліваюцца",
@ -704,7 +704,7 @@
"LabelColorTransfer": "Перадача колеру", "LabelColorTransfer": "Перадача колеру",
"LabelCommunityRating": "Рэйтынг супольнасці", "LabelCommunityRating": "Рэйтынг супольнасці",
"LabelContentType": "Тып кантэнту", "LabelContentType": "Тып кантэнту",
"LabelCountry": "Краіна", "LabelCountry": "Краіна/Рэгіён",
"LabelCreateHttpPortMap": "Уключыць аўтаматычнае адлюстраванне партоў для трафіку HTTP, а таксама HTTPS.", "LabelCreateHttpPortMap": "Уключыць аўтаматычнае адлюстраванне партоў для трафіку HTTP, а таксама HTTPS.",
"LabelCreateHttpPortMapHelp": "Дазволіць аўтаматычнае супастаўленне партоў для стварэння правіла для трафіку HTTP у дадатак да трафіку HTTPS.", "LabelCreateHttpPortMapHelp": "Дазволіць аўтаматычнае супастаўленне партоў для стварэння правіла для трафіку HTTP у дадатак да трафіку HTTPS.",
"LabelCriticRating": "Ацэнка крытыкаў", "LabelCriticRating": "Ацэнка крытыкаў",
@ -1725,7 +1725,7 @@
"LabelSyncPlayNoGroups": "Няма даступных груп", "LabelSyncPlayNoGroups": "Няма даступных груп",
"Notifications": "Апавяшчэнні", "Notifications": "Апавяшчэнні",
"NotificationsMovedMessage": "Функцыянальнасць апавяшчэнняў перанесена ў плагін Webhook.", "NotificationsMovedMessage": "Функцыянальнасць апавяшчэнняў перанесена ў плагін Webhook.",
"AllowSegmentDeletionHelp": "Выдаліць старыя сегменты пасля іх адпраўкі кліенту. Гэта пазбаўляе ад неабходнасці захоўваць увесь перакадаваны файл на дыску. Будзе працаваць толькі з уключаным рэгуляваннем. Выключыце гэта, калі ўзнікнуць праблемы з прайграваннем.", "AllowSegmentDeletionHelp": "Выдаліць старыя сегменты пасля таго, як яны былі загружаны кліентам. Гэта пазбаўляе ад неабходнасці захоўваць увесь перакадаваны файл на дыску. Выключыце гэта, калі ўзнікнуць праблемы з прайграваннем.",
"LabelSystem": "Сістэма", "LabelSystem": "Сістэма",
"HeaderEpisodesStatus": "Статус эпізодаў", "HeaderEpisodesStatus": "Статус эпізодаў",
"LogLevel.None": "Нічога", "LogLevel.None": "Нічога",
@ -1747,7 +1747,7 @@
"LabelThrottleDelaySeconds": "Абмежаваць пасля", "LabelThrottleDelaySeconds": "Абмежаваць пасля",
"LabelThrottleDelaySecondsHelp": "Час у секундах, пасля якога транскодэр будзе затарможаны. Павінен быць дастаткова вялікім, каб кліент падтрымліваў спраўны буфер. Працуе, толькі калі ўключана рэгуляванне.", "LabelThrottleDelaySecondsHelp": "Час у секундах, пасля якога транскодэр будзе затарможаны. Павінен быць дастаткова вялікім, каб кліент падтрымліваў спраўны буфер. Працуе, толькі калі ўключана рэгуляванне.",
"LabelSegmentKeepSeconds": "Час захавання сегментаў", "LabelSegmentKeepSeconds": "Час захавання сегментаў",
"LabelSegmentKeepSecondsHelp": "Час у секундах, на працягу якога сегменты павінны захоўвацца перад іх перазапісам. Павінна быць больш, чым \"Абмежаваць пасля\". Працуе, толькі калі ўключана выдаленне сегмента.", "LabelSegmentKeepSecondsHelp": "Час у секундах, на працягу якога сегменты павінны захоўвацца пасля іх загрузкі кліентам. Працуе, толькі калі ўключана выдаленне сегмента.",
"LabelLevel": "Узровень", "LabelLevel": "Узровень",
"AiTranslated": "У перакладзе ШІ", "AiTranslated": "У перакладзе ШІ",
"MachineTranslated": "Машынны пераклад", "MachineTranslated": "Машынны пераклад",
@ -1767,5 +1767,26 @@
"BackdropScreensaver": "Фонавая застаўка", "BackdropScreensaver": "Фонавая застаўка",
"LabelSelectAudioNormalization": "Нармалізацыя гуку", "LabelSelectAudioNormalization": "Нармалізацыя гуку",
"LabelAlbumGain": "Узмацненне альбома", "LabelAlbumGain": "Узмацненне альбома",
"LogoScreensaver": "Застаўка з лагатыпам" "LogoScreensaver": "Застаўка з лагатыпам",
"ButtonEditUser": "Рэдагаваць карыстальніка",
"LabelJpegQualityHelp": "Якасць сціску JPEG для відарысаў падманнага прайгравання.",
"AirPlay": "AirPlay",
"AllowSubtitleManagement": "Дазволіць гэтаму карыстальніку рэдагаваць субтытры",
"BlockContentWithTagsHelp": "Схаваць мультымедыя хаця б з адным з указаных тэгаў.",
"DlnaMovedMessage": "Функцыянальнасць DLNA перанесена на плагін.",
"ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionSD": "SD",
"ChannelResolutionFullHD": "Full HD",
"ChannelResolutionHD": "HD",
"ChannelResolutionUHD4K": "UHD (4K)",
"DeleteEntireSeries": "Выдаліць {0} эпізодаў",
"DeleteSeries": "Выдаліць серыял",
"DeleteEpisode": "Выдаліць эпізод",
"EnableLibrary": "Уключыць бібліятэку",
"EnableLibraryHelp": "Адключэнне бібліятэкі схавае яе ад усіх карыстальнікаў.",
"LabelJpegQuality": "Якасць JPEG",
"LabelQscale": "Qscale",
"HeaderAllRecordings": "Усе запісы",
"DeleteName": "Выдаліць {0}",
"EnableSmoothScroll": "Уключыць плаўную пракрутку"
} }

View file

@ -753,7 +753,7 @@
"ButtonPlayer": "Reproductor", "ButtonPlayer": "Reproductor",
"ButtonCast": "Transmetre a dispositiu", "ButtonCast": "Transmetre a dispositiu",
"ApiKeysCaption": "Llista de les claus API activades actualment", "ApiKeysCaption": "Llista de les claus API activades actualment",
"AllowTonemappingHelp": "El mapeig de tons pot transformar el rang dinàmic d'un vídeo d'HDR a SDR mantenint els detalls i els colors de la imatge, que són informació molt important per representar l'escena original. Actualment només funciona amb vídeos 10 bit HDR10, HLG o DoVi. Això requereix el temps d'execució OpenCL o CUDA corresponent.", "AllowTonemappingHelp": "El mapeig de tons pot transformar el rang dinàmic d'un vídeo d'HDR a SDR mantenint els detalls i els colors de la imatge, que són informació molt important per representar l'escena original. Actualment només funciona amb vídeos 10 bit HDR10, HLG o DoVi. Això requereix el temps d'execució GPGPU corresponent.",
"ErrorPleaseSelectLineup": "Seleccioneu una alineació i torneu-ho a provar. Si no hi ha cap formació disponible, comproveu que el vostre nom dusuari, contrasenya i codi postal siguin correctes.", "ErrorPleaseSelectLineup": "Seleccioneu una alineació i torneu-ho a provar. Si no hi ha cap formació disponible, comproveu que el vostre nom dusuari, contrasenya i codi postal siguin correctes.",
"ErrorAddingListingsToSchedulesDirect": "S'ha produït un error en afegir la programació al vostre compte de Schedules Direct. Schedules Direct només permet un nombre limitat de programacions per compte. És possible que hagueu diniciar sessió al lloc web de Schedules Direct i eliminar altres llistats del vostre compte abans de continuar.", "ErrorAddingListingsToSchedulesDirect": "S'ha produït un error en afegir la programació al vostre compte de Schedules Direct. Schedules Direct només permet un nombre limitat de programacions per compte. És possible que hagueu diniciar sessió al lloc web de Schedules Direct i eliminar altres llistats del vostre compte abans de continuar.",
"EnableThemeVideosHelp": "Reproduir vídeos temàtics en segon pla mentre navegues per la biblioteca.", "EnableThemeVideosHelp": "Reproduir vídeos temàtics en segon pla mentre navegues per la biblioteca.",
@ -797,7 +797,7 @@
"HeaderAlert": "Alerta", "HeaderAlert": "Alerta",
"HeaderAddUser": "Afegir un usuari", "HeaderAddUser": "Afegir un usuari",
"HeaderAddUpdateSubtitle": "Afegir o actualitzar els subtítols", "HeaderAddUpdateSubtitle": "Afegir o actualitzar els subtítols",
"H264CrfHelp": "El factor de velocitat constant (CRF) és el valor de qualitat predeterminat per als codificadors x264 i x265. Podeu definir els valors entre 0 i 51, on els valors més baixos donarien com a resultat una millor qualitat (a costa de mides de fitxer més altes). Els valors correctes oscil·len entre 18 i 28. El valor predeterminat per a x264 és 23 i per a x265 és 28, de manera que podeu utilitzar-lo com a punt de partida.", "H264CrfHelp": "El factor de velocitat constant (CRF) és el valor de qualitat predeterminat per als codificadors de programari x264 i x265. Podeu definir els valors entre 0 i 51, on els valors més baixos donarien com a resultat una millor qualitat (a costa de mides de fitxer més altes). Els valors correctes oscil·len entre 18 i 28. El valor predeterminat per a x264 és 23 i per a x265 és 28, de manera que podeu utilitzar-lo com a punt de partida. Els codificadors per maquinari no utilitzen aquesta configuració.",
"GuideProviderSelectListings": "Seleccionar Llistats", "GuideProviderSelectListings": "Seleccionar Llistats",
"GroupVersions": "Grups de versions", "GroupVersions": "Grups de versions",
"Framerate": "Velocitat de fotogrames", "Framerate": "Velocitat de fotogrames",
@ -1667,7 +1667,7 @@
"RememberAudioSelectionsHelp": "Intentar establir la pista d'àudio amb la coincidència més semblant a l'últim vídeo.", "RememberAudioSelectionsHelp": "Intentar establir la pista d'àudio amb la coincidència més semblant a l'últim vídeo.",
"RememberSubtitleSelectionsHelp": "Intentar configurar la pista de subtítols amb la coincidència més semblant a l'últim vídeo.", "RememberSubtitleSelectionsHelp": "Intentar configurar la pista de subtítols amb la coincidència més semblant a l'últim vídeo.",
"RememberAudioSelections": "Establir la pista d'àudio en funció de l'element anterior", "RememberAudioSelections": "Establir la pista d'àudio en funció de l'element anterior",
"EnableEnhancedNvdecDecoderHelp": "Implementació experimental de NVDEC, no activeu aquesta opció tret que trobeu errors de descodificació.", "EnableEnhancedNvdecDecoderHelp": "Implementació millorada de NVDEC, desactiveu aquesta opció per a utilitzar CUVID si trobeu errors de descodificació.",
"LabelVppTonemappingBrightness": "Guany de brillantor del mapa de to VPP", "LabelVppTonemappingBrightness": "Guany de brillantor del mapa de to VPP",
"LabelVppTonemappingContrast": "Guany de contrast de mapatge de to VPP", "LabelVppTonemappingContrast": "Guany de contrast de mapatge de to VPP",
"LabelVppTonemappingContrastHelp": "Aplicar el guany de contrast a l'assignació de to VPP. Tant el valor recomanat com el predeterminat són 1.", "LabelVppTonemappingContrastHelp": "Aplicar el guany de contrast a l'assignació de to VPP. Tant el valor recomanat com el predeterminat són 1.",
@ -1788,11 +1788,80 @@
"LabelBuildVersion": "Versió de compilació", "LabelBuildVersion": "Versió de compilació",
"LabelUseReplayGainTagsHelp": "Escaneja els fitxers d'àudio per trobar etiquetes ReplayGain i fes-les servir en lloc de calcular el valor LUFS. (Utilitza menys potència. Invalidarà l'opció 'Escaneig LUFS')", "LabelUseReplayGainTagsHelp": "Escaneja els fitxers d'àudio per trobar etiquetes ReplayGain i fes-les servir en lloc de calcular el valor LUFS. (Utilitza menys potència. Invalidarà l'opció 'Escaneig LUFS')",
"EnableVideoToolboxTonemapping": "Habilitar el mapatge de tons de VideoToolbox", "EnableVideoToolboxTonemapping": "Habilitar el mapatge de tons de VideoToolbox",
"AllowVideoToolboxTonemappingHelp": "Acceleració per maquinari en el mapatge de tons per VideoToolbox. Funciona amb la majoria dels formats HDR, inclosos HDR10, HDR10+ i HLG, però no funciona amb Dolby Vision Profile 5. Això té una prioritat més alta en comparació amb una altra implementació d'OpenCL.", "AllowVideoToolboxTonemappingHelp": "Acceleració per maquinari en el mapatge de tons per VideoToolbox. Funciona amb la majoria dels formats HDR, inclosos HDR10, HDR10+ i HLG, però no funciona amb Dolby Vision Profile 5. Això té una prioritat més alta en comparació amb una altra implementació de Metal.",
"LabelUseReplayGainTags": "Utilitzar etiquetes ReplayGain", "LabelUseReplayGainTags": "Utilitzar etiquetes ReplayGain",
"ChannelResolutionSDPAL": "SD (PAL)", "ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionHD": "HD", "ChannelResolutionHD": "HD",
"DlnaMovedMessage": "La funcionalitat DLNA s'ha mogut a complements.", "DlnaMovedMessage": "La funcionalitat DLNA s'ha mogut a complements.",
"AllowSubtitleManagement": "Permetre a aquest usuari editar els subtítols", "AllowSubtitleManagement": "Permetre a aquest usuari editar els subtítols",
"ChannelResolutionUHD4K": "UHD (4K)" "ChannelResolutionUHD4K": "UHD (4K)",
"LabelTrickplayAccel": "Habilitar descodificació per maquinari",
"NonBlockingScan": "Sense Bloqueig - generació de cues, després retorna",
"ConfirmDeleteSeries": "L'esborrat d'aquesta sèrie esborrarà TOTS {0} episodis tant del sistema de fitxers com de la teva biblioteca de mitjans. Estàs segur que vols continuar?",
"LabelEncodingFormatOptions": "Opcions de format de codificació",
"LabelTrickplayAccelHelp": "Assegura't d'habilitar 'Permetre codificació MJPEG' a Transcodificació si el teu maquinari ho suporta.",
"LabelWidthResolutions": "Resolucions d'Amplada",
"LabelScanBehaviorHelp": "El comportament predeterminat és sense blocatge, que afegirà mitjans a la biblioteca abans que la generació de trickplay es completi. El bloqueig assegura que els fitxers de trickplay es generin abans que els mitjans s'afegeixin a la biblioteca, però farà que l'escaneig sigui més llarg.",
"LabelProcessPriorityHelp": "Establint-ho més baix o més alt determinarà com el processador prioritza el procés de generació de trickplay ffmpeg en relació amb altres processos. Si observeu una ralentització mentre genereu imatges de trickplay, però no voleu aturar completament la seva generació, proveu de reduir-la així com el nombre de fils.",
"AirPlay": "AirPlay",
"AllowContentWithTagsHelp": "Només mostrar mitjans amb almenys una etiqueta especificada.",
"BlockContentWithTagsHelp": "Amagar mitjans amb almenys una etiqueta especificada.",
"LabelAllowContentWithTags": "Permetre elements amb etiquetes",
"BlockingScan": "Bloqueig - generació de cues, bloqueja escaneig fins a completar-se",
"LabelScanBehavior": "Comportament d'Escaneig",
"PlaybackError.ASS_RENDER_ERROR": "S'ha produït un error al renderitzador de subtítols ASS/SSA.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "La reproducció ha fallat, ja que el mitjà no és compatible amb aquest client.",
"PlaybackError.NETWORK_ERROR": "La reproducció ha fallat a causa d'un error de connexió.",
"PlaybackError.NO_MEDIA_ERROR": "No s'ha pogut trobar un mitjà vàlid per a reproduir.",
"PlaybackError.PLAYER_ERROR": "La reproducció ha fallat a causa d'un error fatal del reproductor.",
"PlaybackError.SERVER_ERROR": "La reproducció ha fallat a causa d'un error del servidor.",
"PlaybackError.NotAllowed": "La reproducció d'aquest mitjà no està permesa.",
"Trickplay": "Trickplay",
"PriorityHigh": "Alta",
"EnableLibrary": "Habilitar la biblioteca",
"EnableLibraryHelp": "Deshabilitar la biblioteca l'amagarà per a tots els usuaris.",
"LimitSupportedVideoResolution": "Limitar la resolució màxima de vídeo suportada",
"LimitSupportedVideoResolutionHelp": "Utilitzi \"Resolució de transcodificació de vídeo màxima permesa\" com a resolució màxima permesa.",
"PlaybackError.FATAL_HLS_ERROR": "S'ha produït un error fatal al stream HLS.",
"PlaybackError.RateLimitExceeded": "Aquest mitjà no es pot reproduir en aquests instants deguts a límits de la xarxa.",
"EncodingFormatHelp": "Seleccioni la codificació de vídeo a la qual hauria de transcodificar Jellyfin. Jellyfin utilitzarà codificació mitjançant programari quan l'acceleració per maquinari no estigui disponible. La codificació H264 sempre estarà habilitada.",
"PriorityAboveNormal": "Per sobre del Normal",
"PriorityNormal": "Normal",
"LabelImageIntervalHelp": "Interval de temps (ms) entre cada imatge nova de trickplay.",
"LabelWidthResolutionsHelp": "Llista separada per comes de les amplades (px) en què es generaran les imatges de trickplay. Totes les imatges s'han de generar proporcionalment a la font, de manera que una amplada de 320 en un vídeo 16:9 acaba al voltant de 320x180.",
"LabelTileWidth": "Amplada del Títol",
"LabelTileWidthHelp": "Nombre màxim d'imatges per casella en la direcció X.",
"LabelTileHeight": "Alçada del Títol",
"LabelTileHeightHelp": "Nombre màxim d'imatges per casella en la direcció Y.",
"LabelJpegQuality": "Qualitat JPEG",
"LabelJpegQualityHelp": "Qualitat de compressió JPEG per a les imatges de trickplay.",
"LabelQscale": "Qscale",
"OptionExtractTrickplayImage": "Habilitar extracció d'imatges de trickplay",
"LabelExtractTrickplayDuringLibraryScan": "Extreure imatges de trickplay durant l'escaneig de la biblioteca",
"LabelExtractTrickplayDuringLibraryScanHelp": "Generar imatges de trickplay quan els vídeos són importats durant l'escaneig de la biblioteca. D'altra banda, seran extrets durant la tasca programada d'imatges de trickplay. Si la generació està marcada com a sense bloqueig, això no afectarà el temps que triga a completar l'exploració de la biblioteca.",
"DeleteEntireSeries": "Esborrar {0} Episodis",
"DeleteSeries": "Esborrar Sèries",
"DeleteEpisode": "Esborrar Episodi",
"PriorityIdle": "Parada",
"LabelProcessPriority": "Prioritat de Processos",
"LabelTrickplayThreadsHelp": "Nombre de fils a passar a l'argument de ffmpleg '-threads'.",
"Lyric": "Lletra",
"EnableSmoothScroll": "Habilitar desplaçament suau",
"HeaderDeleteSeries": "Esborrar Sèries",
"PlaybackError.MEDIA_DECODE_ERROR": "La reproducció ha fallat a causa d'un error en descodificar el contingut.",
"AllowMjpegEncoding": "Permetre codificació en format MJPEG (utilitzat durant la generació de trickplay)",
"PriorityBelowNormal": "Per sota del Normal",
"LabelImageInterval": "Interval d'Imatges",
"LabelQscaleHelp": "Escala de qualitat de les imatges generades per ffmpeg, sent 2 la qualitat més alta i 31 la més baixa.",
"LabelTrickplayThreads": "Fils de FFmpeg",
"ExtractTrickplayImagesHelp": "Les imatges de Trickplay són similars a les imatges de capítols, excepte que abasten tota la longitud del contingut i s'utilitzen per mostrar una vista prèvia quan es desplaça pels vídeos.",
"LabelTrickplayAccelEncodingHelp": "En aquests moments només està disponible en QSV i VAAPI, aquesta opció no afecta els altres mètodes d'acceleració per maquinari.",
"LabelTrickplayAccelEncoding": "Habilitar codificació MJPEG accelerada per maquinari",
"ConfirmDeleteLyrics": "L'esborrat d'aquestes lletres, les suprimirà tant del sistema de fitxers com de la vostra biblioteca multimèdia. Esteu segur que voleu continuar?",
"ErrorDeletingLyrics": "S'ha produït un error en suprimir les lletres del servidor. Si us plau, comproveu que Jellyfin tingui accés d'escriptura a la carpeta multimèdia i torneu-ho a provar.",
"HeaderDeleteLyrics": "Esborrar Lletres",
"DeleteLyrics": "Esborrar lletres",
"HeaderNoLyrics": "No s'ha trobat cap lletra",
"Lyrics": "Lletres",
"ViewLyrics": "Veure lletres"
} }

View file

@ -171,7 +171,7 @@
"Guide": "Programový průvodce", "Guide": "Programový průvodce",
"GuideProviderLogin": "Přihlášení", "GuideProviderLogin": "Přihlášení",
"GuideProviderSelectListings": "Výběr zobrazení", "GuideProviderSelectListings": "Výběr zobrazení",
"H264CrfHelp": "Konstantní faktor toku (CRF) je výchozím nastavení kvality pro enkodér x264 a x265. Hodnoty mohou být v rozmezí 0-51, kde nižší hodnoty znamenají vyšší kvalitu na úkor větší velikosti souborů. Rozumné hodnoty jsou 18-28. Výchozí hodnoty jsou 23 pro x264 a 28 pro x265, které můžete použít jako výchozí body.", "H264CrfHelp": "Konstantní faktor toku (CRF) je výchozím nastavení kvality pro softwarové enkodéry x264 a x265. Hodnoty mohou být v rozmezí 0-51, kde nižší hodnoty znamenají vyšší kvalitu na úkor větší velikosti souborů. Rozumné hodnoty jsou 18-28. Výchozí hodnoty jsou 23 pro x264 a 28 pro x265, které můžete použít jako výchozí body. Hardwarové enkodéry tyto nastavení nepoužívají.",
"EncoderPresetHelp": "Vyber rychlejší hodnoty ke zvýšení výkonu, nebo pomalejší ke zvýšení kvality.", "EncoderPresetHelp": "Vyber rychlejší hodnoty ke zvýšení výkonu, nebo pomalejší ke zvýšení kvality.",
"HDPrograms": "HD programy", "HDPrograms": "HD programy",
"HardwareAccelerationWarning": "Zapnutí hardwarové akcelerace může způsobit nestabilitu v některých prostředích. Ujistěte se, že váš operační systém a grafické ovladače jsou aktuální. Máte-li potíže s přehráváním videa po zapnutí této funkce, budete muset změnit nastavení zpět na Žádné.", "HardwareAccelerationWarning": "Zapnutí hardwarové akcelerace může způsobit nestabilitu v některých prostředích. Ujistěte se, že váš operační systém a grafické ovladače jsou aktuální. Máte-li potíže s přehráváním videa po zapnutí této funkce, budete muset změnit nastavení zpět na Žádné.",
@ -1386,7 +1386,7 @@
"LabelTonemappingRange": "Rozsah mapování tónů", "LabelTonemappingRange": "Rozsah mapování tónů",
"TonemappingAlgorithmHelp": "Mapování tonů je možné dále ladit. Pokud možnostem zde nerozumíte, je možné ponechat vše ve výchozím nastavení. Doporučená hodnota je 'BT.2390'.", "TonemappingAlgorithmHelp": "Mapování tonů je možné dále ladit. Pokud možnostem zde nerozumíte, je možné ponechat vše ve výchozím nastavení. Doporučená hodnota je 'BT.2390'.",
"LabelTonemappingAlgorithm": "Algoritmus mapování tónů", "LabelTonemappingAlgorithm": "Algoritmus mapování tónů",
"AllowTonemappingHelp": "Mapování tónů umožňuje změnit dynamický rozsah videa z HDR na SDR bez ztráty důležitých informací původního obrazu, jako jsou detaily a barvy. Tato funkce momentálně funguje pouze u videí, které obsahují 10bitové HDR10, HLG nebo Dolby Vision. Funkce rovněž vyžaduje OpenCL nebo CUDA.", "AllowTonemappingHelp": "Mapování tónů umožňuje změnit dynamický rozsah videa z HDR na SDR bez ztráty důležitých informací původního obrazu, jako jsou detaily a barvy. Tato funkce momentálně funguje pouze u videí, které obsahují 10bitové HDR10, HLG nebo Dolby Vision. Funkce rovněž vyžaduje příslušný software od výrobce grafické karty.",
"EnableTonemapping": "Zapnout mapování tónů", "EnableTonemapping": "Zapnout mapování tónů",
"LabelOpenclDeviceHelp": "Zařízení OpenCL použité pro mapování tónů. Nalevo od tečky je číslo platformy, napravo pak číslo zařízení na této platformě. Výchozí hodnota je 0.0. Soubor aplikace FFmpeg, který obsahuje metodu pro hardwarovou akceleraci OpenCL, je povinný.", "LabelOpenclDeviceHelp": "Zařízení OpenCL použité pro mapování tónů. Nalevo od tečky je číslo platformy, napravo pak číslo zařízení na této platformě. Výchozí hodnota je 0.0. Soubor aplikace FFmpeg, který obsahuje metodu pro hardwarovou akceleraci OpenCL, je povinný.",
"LabelOpenclDevice": "Zařízení OpenCL", "LabelOpenclDevice": "Zařízení OpenCL",
@ -1649,7 +1649,7 @@
"OriginalAirDate": "Původní datum vysílání", "OriginalAirDate": "Původní datum vysílání",
"Digital": "Digitální", "Digital": "Digitální",
"MessageUnauthorizedUser": "Momentálně nemáte oprávnění k přístupu na server. Pro více informací kontaktujte Vašeho správce serveru.", "MessageUnauthorizedUser": "Momentálně nemáte oprávnění k přístupu na server. Pro více informací kontaktujte Vašeho správce serveru.",
"EnableEnhancedNvdecDecoderHelp": "Experimentální implementace NVDEC. Používejte jen v případě, že dochází při dekódování k chybám.", "EnableEnhancedNvdecDecoderHelp": "Vylepšená implementace NVDEC. Pokud dochází při dekódování k chybám, vypnutím této možnosti bude použito CUVID.",
"HomeVideosPhotos": "Domácí videa a fotky", "HomeVideosPhotos": "Domácí videa a fotky",
"Bold": "Tučné", "Bold": "Tučné",
"LabelTextWeight": "Tloušťka textu", "LabelTextWeight": "Tloušťka textu",
@ -1794,9 +1794,78 @@
"ChannelResolutionFullHD": "Full HD", "ChannelResolutionFullHD": "Full HD",
"ChannelResolutionUHD4K": "UHD (4K)", "ChannelResolutionUHD4K": "UHD (4K)",
"EnableVideoToolboxTonemapping": "Povolit mapování tónů VideoToolbox", "EnableVideoToolboxTonemapping": "Povolit mapování tónů VideoToolbox",
"AllowVideoToolboxTonemappingHelp": "Hardwarově akcelerované mapování tónů pomocí VideoToolbox. Funguje s většinou formátů HDR, včetně HDR10, HDR10+ a HLG, ale nefunguje s Dolby Vision Profile 5. Má vyšší prioritu než jiné implementace OpenCL.", "AllowVideoToolboxTonemappingHelp": "Hardwarově akcelerované mapování tónů pomocí VideoToolbox. Funguje s většinou formátů HDR, včetně HDR10, HDR10+ a HLG, ale nefunguje s Dolby Vision Profile 5. Má vyšší prioritu než jiné implementace Metal.",
"DeleteName": "Odstranit {0}", "DeleteName": "Odstranit {0}",
"LabelUseReplayGainTagsHelp": "Zjistí, zda zvukové souboru obsahují informace o ReplayGain, a použije je místo výpočtu hodnoty LUFS. (Je méně výpočetně náročné. Nahradí možnost \"Skenování LUFS\")", "LabelUseReplayGainTagsHelp": "Zjistí, zda zvukové souboru obsahují informace o ReplayGain, a použije je místo výpočtu hodnoty LUFS. (Je méně výpočetně náročné. Nahradí možnost \"Skenování LUFS\")",
"LabelUseReplayGainTags": "Použít ReplayGain", "LabelUseReplayGainTags": "Použít ReplayGain",
"AllowSubtitleManagement": "Povolit tomuto uživateli upravovat titulky" "AllowSubtitleManagement": "Povolit tomuto uživateli upravovat titulky",
"LabelAllowContentWithTags": "Povolit položky se značkami",
"AllowMjpegEncoding": "Povolit kódování do formátu MJPEG (používá se při generování náhledů videa)",
"Trickplay": "Náhledy videa",
"LabelTrickplayAccel": "Povolit hardwarové dekódování",
"PriorityHigh": "Vysoká",
"LabelImageInterval": "Interval mezi náhledy",
"PlaybackError.ASS_RENDER_ERROR": "Při zobrazování titulků ASS/SSA došlo k chybě.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Přehrávání selhalo, protože médium není podporování tímto klientem.",
"PlaybackError.MEDIA_DECODE_ERROR": "Přehrávání selhalo kvůli chybě při dekódování videa.",
"PlaybackError.SERVER_ERROR": "Přehrávání selhalo kvůli chybě serveru.",
"PlaybackError.PLAYER_ERROR": "Přehrávání selhalo kvůli kritické chybě přehrávače.",
"LabelWidthResolutions": "Šířky rozlišení",
"LabelTrickplayAccelHelp": "Pokud to váš hardware umožňuje, povolte možnost \"Povolit kódování do formátu MJPEG\" v sekci Překódování.",
"LabelProcessPriorityHelp": "Určuje prioritu procesu generování náhledů videí v porovnání s ostatními procesy. Pokud zaznamenáte při generování náhledů zpomalení systému, ale nechcete generování úplně zastavit, zkuste snížit prioritu i počet vláken.",
"DeleteSeries": "Odstranit seriál",
"ConfirmDeleteSeries": "Opravdu chcete odstranit tento seriál a smazat všech {0} dílů z disku i z knihovny médií?",
"DeleteEntireSeries": "Smazat {0} dílů",
"DeleteEpisode": "Odstranit díl",
"PriorityAboveNormal": "Zvýšená",
"PriorityNormal": "Normální",
"LabelScanBehaviorHelp": "Výchozí nastavení je neblokující, tzn. média jsou do knihovny přidány před dokončením generování náhledů. Blokující chování zajistí, že jsou náhledy vygenerovány před přidáním médií do knihovny, ale výrazně prodlužuje dobu skenování.",
"LabelQscaleHelp": "Kvalita náhledů vygenerovaných pomocí ffmpeg. 2 je nejvyšší kvalita a 31 je nejnižší kvalita.",
"LabelTileHeightHelp": "Maximální počet náhledů na dlaždici na ose Y.",
"LabelJpegQuality": "Kvalita JPEG",
"LabelJpegQualityHelp": "Kvalita komprese JPEG pro náhledy videí.",
"LabelQscale": "Qscale",
"LabelTrickplayThreadsHelp": "Počet vláken, které jsou předány argumentu \"-threads\" aplikace ffmpeg.",
"OptionExtractTrickplayImage": "Povolit generování náhledů videí",
"LabelTrickplayThreads": "Počet vláken ffmpeg",
"ExtractTrickplayImagesHelp": "Náhledy videí jsou podobné náhledům kapitol, ale pokrývají celou délku videa. Používají se pro zobrazení náhledů při přetáčení videa.",
"LabelExtractTrickplayDuringLibraryScan": "Generovat náhledy videí při skenování knihovny",
"LabelExtractTrickplayDuringLibraryScanHelp": "Vygeneruje náhledy videí během skenování knihovny. Pokud je tato možnost vypnuta, náhledy budou vygenerovány během naplánované úlohy. Pokud je generování nastaveno na neblokující, nebude tato možnost mít vliv na délku skenování knihovny.",
"Lyric": "Texty",
"NonBlockingScan": "Neblokující - vloží generování do fronty, a poté pokračuje",
"BlockingScan": "Blokující - vloží generování do fronty, a pozastaví skenování dokud není dokončeno",
"LabelScanBehavior": "Chování skenování",
"PriorityBelowNormal": "Snížená",
"LabelImageIntervalHelp": "Interval v milisekundách mezi jednotlivými náhledy.",
"EnableSmoothScroll": "Povolit plynulé skrolování",
"HeaderDeleteSeries": "Odstranit seriál",
"PlaybackError.FATAL_HLS_ERROR": "Ve streamu HLS nastala kritická chyba.",
"PlaybackError.NETWORK_ERROR": "Přehrávání selhalo kvůli chybě sítě.",
"PlaybackError.NotAllowed": "Přehrávání tohoto média není povoleno.",
"LabelTileWidth": "Velikost dlaždice",
"LabelTileWidthHelp": "Maximální počet náhledů na dlaždici na ose X.",
"PriorityIdle": "Při neaktivitě",
"LabelProcessPriority": "Priorita procesu",
"LabelWidthResolutionsHelp": "Čárkou oddělený seznam šířek v pixelech, které určují šířku vygenerovaných náhledů videa. Všechny náhledy by měly mít stejné proporce jako zdrojové video, tzn. zadaná šířka o hodnotě 320 u videa s poměrem stran 16:9 bude mít za následek náhled o velikosti 320x180.",
"LabelTileHeight": "Výška dlaždice",
"LimitSupportedVideoResolution": "Omezit maximální podporované rozlišení videa",
"LimitSupportedVideoResolutionHelp": "Použít \"Maximální rozlišení videa pro překódování\" jako maximální podporované rozlišení videa.",
"PlaybackError.NO_MEDIA_ERROR": "Nelze najít platný zdroj médií k přehrání.",
"PlaybackError.RateLimitExceeded": "Toto médium není možné momentálně přehrát kvůli omezení rychlosti.",
"AllowContentWithTagsHelp": "Zobrazovat jen média, která mají alespoň jednu ze značek.",
"BlockContentWithTagsHelp": "Skrýt média, která mají alespoň jednu ze značek.",
"AirPlay": "AirPlay",
"LabelEncodingFormatOptions": "Možnosti formátu kódování",
"EncodingFormatHelp": "Vyberte formát, do kterého by měly být překódovány videa. Pokud není k dispozici hardwarová akcelerace pro vybraný formát, bude použito softwarové kódování. Kódování do formátu H.264 bude vždy povoleno.",
"LabelTrickplayAccelEncoding": "Povolit hardwarově akcelerované kódování MJPEG",
"LabelTrickplayAccelEncodingHelp": "Tato možnost je momentálně dostupná pouze při použití QSV a VAAPI. Nemá vliv na ostatní hardwarovou akceleraci.",
"EnableLibrary": "Povolit knihovnu",
"EnableLibraryHelp": "Vypnutím knihovny ji skryjete ze všech míst, kde je uživateli zobrazena.",
"ConfirmDeleteLyrics": "Písně budou odstraněny ze systému i knihovny médií. Opravdu chcete pokračovat?",
"DeleteLyrics": "Odstranit texty písní",
"ErrorDeletingLyrics": "Při odstraňování textů písní ze serveru došlo k chybě. Zkontrolujte, zda-li má Jellyfin oprávnění k zápisu do složky s médii a zkuste to znovu.",
"HeaderDeleteLyrics": "Odstranit texty písní",
"HeaderNoLyrics": "Žádné texty písní nebyly nalezeny",
"Lyrics": "Texty písní",
"ViewLyrics": "Zobrazit texty písní"
} }

View file

@ -141,7 +141,7 @@
"GroupVersions": "Grupér versioner", "GroupVersions": "Grupér versioner",
"GuestStar": "Gæsteskuespiller", "GuestStar": "Gæsteskuespiller",
"GuideProviderSelectListings": "Vælg udbyder", "GuideProviderSelectListings": "Vælg udbyder",
"H264CrfHelp": "Den Konstante Ratefaktor (CRF) er standardindstillingen for X264-koderen og X265-koderen. Du kan sætte værdien i mellem 0 og 51, hvor de lavere værdier resulterer i bedre kvalitet (på bekostning af større filstørrelser). Fornuftige værdier er i mellem 18 og 28. Standarden for X264 er 23 og for Z265 er 28, så du kan bruge dette som udgangspunkt.", "H264CrfHelp": "Den Konstante Ratefaktor (CRF) er standardindstillingen for X264 og X265 software kodere. Du kan sætte værdien i mellem 0 og 51, hvor de lavere værdier resulterer i bedre kvalitet (på bekostning af større filstørrelser). Fornuftige værdier er i mellem 18 og 28. Standarden for X264 er 23 og for Z265 er 28, så du kan bruge dette som udgangspunkt. Hardware kodere bruger ikke disse indstillinger.",
"EncoderPresetHelp": "Vælg en hurtigere værdi for at forbedre ydeevne, eller en langsommere værdi for at forbedre kvalitet.", "EncoderPresetHelp": "Vælg en hurtigere værdi for at forbedre ydeevne, eller en langsommere værdi for at forbedre kvalitet.",
"HDPrograms": "HD-programmer", "HDPrograms": "HD-programmer",
"HardwareAccelerationWarning": "Aktivering af hardwareacceleration kan forårsage ustabilitet i nogle miljøer. Kontroller at dit operativsystem og videodriver er opdateret. Hvis du har problemer med at afspille video efter aktivering af dette, bliver du nød til at skifte tilbage til Ingen.", "HardwareAccelerationWarning": "Aktivering af hardwareacceleration kan forårsage ustabilitet i nogle miljøer. Kontroller at dit operativsystem og videodriver er opdateret. Hvis du har problemer med at afspille video efter aktivering af dette, bliver du nød til at skifte tilbage til Ingen.",
@ -1128,7 +1128,7 @@
"PlayCount": "Afspilninger", "PlayCount": "Afspilninger",
"PlayNext": "Afspil næste", "PlayNext": "Afspil næste",
"PlayNextEpisodeAutomatically": "Afspil næste afsnit automatisk", "PlayNextEpisodeAutomatically": "Afspil næste afsnit automatisk",
"PlaybackErrorNoCompatibleStream": "Denne klient er ikke kompatibel med medierne, og serveren sender ikke et kompatibelt medieformat.", "PlaybackErrorNoCompatibleStream": "Denne klient er ikke kompatibel med mediet, og serveren sender ikke et kompatibelt medieformat.",
"Playlists": "Afspilningslister", "Playlists": "Afspilningslister",
"Previous": "Forrige", "Previous": "Forrige",
"Primary": "Primær", "Primary": "Primær",
@ -1311,7 +1311,7 @@
"EnableDetailsBanner": "Detalje banner", "EnableDetailsBanner": "Detalje banner",
"EnableDecodingColorDepth10Vp9": "Aktiver 10-bit hardware dekodning for VP9", "EnableDecodingColorDepth10Vp9": "Aktiver 10-bit hardware dekodning for VP9",
"EnableDecodingColorDepth10Hevc": "Aktiver 10-bit hardware dekodning for HEVC", "EnableDecodingColorDepth10Hevc": "Aktiver 10-bit hardware dekodning for HEVC",
"AllowTonemappingHelp": "Tonekortlægning kan omdanne det dynamiske område for en video fra HDR til SDR, samtidig med at billeddetaljer og farver opretholdes, hvilket er meget vigtig information til at repræsentere den originale scene. Fungerer i øjeblikket kun, når der omkodes videoer med indlejret HDR10- eller HLG-metadata. Dette kræver den tilsvarende OpenCL eller CUDA runtime.", "AllowTonemappingHelp": "Tonekortlægning kan omdanne det dynamiske område for en video fra HDR til SDR, samtidig med at billeddetaljer og farver opretholdes, hvilket er meget vigtig information til at repræsentere den originale scene. Fungerer i øjeblikket kun, når der omkodes videoer med indlejret HDR10- eller HLG-metadata. Dette kræver den tilsvarende GPGPU runtime.",
"LabelCurrentStatus": "Aktuel status", "LabelCurrentStatus": "Aktuel status",
"LabelChromecastVersion": "Google Cast-version", "LabelChromecastVersion": "Google Cast-version",
"LabelAutomaticDiscoveryHelp": "Tillad, at applikationer automatisk finder Jellyfin ved hjælp af UDP-port 7359.", "LabelAutomaticDiscoveryHelp": "Tillad, at applikationer automatisk finder Jellyfin ved hjælp af UDP-port 7359.",
@ -1365,7 +1365,7 @@
"QuickConnectInvalidCode": "Ugyldig Quick Connect kode", "QuickConnectInvalidCode": "Ugyldig Quick Connect kode",
"QuickConnectDescription": "For at logge ind med Quick Connect, vælg da 'Quick Connect' knappen på den enhed du er logget ind fra og indtast den viste kode nedenfor.", "QuickConnectDescription": "For at logge ind med Quick Connect, vælg da 'Quick Connect' knappen på den enhed du er logget ind fra og indtast den viste kode nedenfor.",
"QuickConnectDeactivated": "Quick Connect blev deaktiveret før log ind anmodningen kunne blive godkendt", "QuickConnectDeactivated": "Quick Connect blev deaktiveret før log ind anmodningen kunne blive godkendt",
"QuickConnectAuthorizeSuccess": "Succes! Din enhed er blevet godkendt.", "QuickConnectAuthorizeSuccess": "Du har godkendt din enhed med succes!",
"QuickConnectAuthorizeFail": "Ukendt Quick Connect kode", "QuickConnectAuthorizeFail": "Ukendt Quick Connect kode",
"QuickConnectAuthorizeCode": "Angiv kode {0} for at logge ind", "QuickConnectAuthorizeCode": "Angiv kode {0} for at logge ind",
"QuickConnectActivationSuccessful": "Aktivering blev gennemført", "QuickConnectActivationSuccessful": "Aktivering blev gennemført",
@ -1452,7 +1452,7 @@
"PreferFmp4HlsContainerHelp": "Brug helst fMP4 som default beholder for HLS, hvilket gør det muligt at streame HEVC og AV1 indhold direkte på understøttede enheder.", "PreferFmp4HlsContainerHelp": "Brug helst fMP4 som default beholder for HLS, hvilket gør det muligt at streame HEVC og AV1 indhold direkte på understøttede enheder.",
"RemuxHelp2": "Remux bruger meget lidt processor kraft med komplet tabsfri medie kvalitet.", "RemuxHelp2": "Remux bruger meget lidt processor kraft med komplet tabsfri medie kvalitet.",
"RemuxHelp1": "Medie filen er i en ikke-kompatibel fil beholder (MKV, AVI, WMV, osv) men både video og lys strømmene er kompatible med enheden. Medie filen vil blive genpakket tabsfrit før det sendes til enheden.", "RemuxHelp1": "Medie filen er i en ikke-kompatibel fil beholder (MKV, AVI, WMV, osv) men både video og lys strømmene er kompatible med enheden. Medie filen vil blive genpakket tabsfrit før det sendes til enheden.",
"LabelFallbackFontPathHelp": "Definer en sti der indeholder tilbagefalds skrifttyper til rendering af ASS/SSA undertekster. Maksimalt størrelse tilladt er 20 MB. Letvægts og web-vendlige skrifttyper som woff2 er anbefalet.", "LabelFallbackFontPathHelp": "Disse skrifttyper bruges af nogle klienter til at rendere undertekster. Se venligst dokumentationen for mere information.",
"LabelFallbackFontPath": "Sti til tilbagefaldsmappen for skrifttyper", "LabelFallbackFontPath": "Sti til tilbagefaldsmappen for skrifttyper",
"HeaderSelectFallbackFontPathHelp": "Gennemse eller skriv stien til tilbagefaldsmappen for skrifttyper til brug når der renderes ASS/SSA undertekster.", "HeaderSelectFallbackFontPathHelp": "Gennemse eller skriv stien til tilbagefaldsmappen for skrifttyper til brug når der renderes ASS/SSA undertekster.",
"HeaderSelectFallbackFontPath": "Vælg Tilbagefalds Skrifttype Mappesti", "HeaderSelectFallbackFontPath": "Vælg Tilbagefalds Skrifttype Mappesti",
@ -1647,7 +1647,7 @@
"ButtonExitApp": "Afslut programmet", "ButtonExitApp": "Afslut programmet",
"ButtonClose": "Luk", "ButtonClose": "Luk",
"AddToFavorites": "Tilføj som favorit", "AddToFavorites": "Tilføj som favorit",
"EnableEnhancedNvdecDecoderHelp": "Eksperimentel NVDEC-implementering. Slå ikke denne indstilling til, med mindre du oplever afkodningsfejl.", "EnableEnhancedNvdecDecoderHelp": "Udvidet NVDEC-implementering, slå denne indstilling fra for at bruge CUVID hvis du oplever afkodningsfejl.",
"DownloadAll": "Hent alle", "DownloadAll": "Hent alle",
"ScreenResolution": "Skærmopløsning", "ScreenResolution": "Skærmopløsning",
"SecondarySubtitles": "Sekundære undertekster", "SecondarySubtitles": "Sekundære undertekster",
@ -1677,10 +1677,10 @@
"HomeVideosPhotos": "Hjemmevideoer og -billeder", "HomeVideosPhotos": "Hjemmevideoer og -billeder",
"IgnoreDtsHelp": "Deaktivering af denne indstilling kan muligvis løse udfordringer såsom manglende lydkanal med separate lyd- og videospor.", "IgnoreDtsHelp": "Deaktivering af denne indstilling kan muligvis løse udfordringer såsom manglende lydkanal med separate lyd- og videospor.",
"EnableSplashScreen": "Vis opstarts skærm", "EnableSplashScreen": "Vis opstarts skærm",
"LabelStereoDownmixAlgorithm": "", "LabelStereoDownmixAlgorithm": "Stereo Nedskalerings Algoritme",
"LabelMaxVideoResolution": "Max opløsning af video omkodnings opløsning", "LabelMaxVideoResolution": "Max opløsning af video omkodnings opløsning",
"HeaderRecordingMetadataSaving": "Optager metadata", "HeaderRecordingMetadataSaving": "Optager metadata",
"LabelChapterImageResolutionHelp": "Opløsningen af det udtrukne kapitelbillede. Ændringen af dette vil ingen effekt have på dummy kapitler", "LabelChapterImageResolutionHelp": "Opløsningen af det udtrukne kapitelbillede. Ændringen af dette vil ingen effekt have på dummy kapitler.",
"MessageRenameMediaFolder": "Omdøbning af et mediebibliotek sletter al tilhørende metadata. Fortsæt med forsigtighed.", "MessageRenameMediaFolder": "Omdøbning af et mediebibliotek sletter al tilhørende metadata. Fortsæt med forsigtighed.",
"StereoDownmixAlgorithmHelp": "Algoritme brugt til at nedskalere flerkanals lyd til stereo.", "StereoDownmixAlgorithmHelp": "Algoritme brugt til at nedskalere flerkanals lyd til stereo.",
"IgnoreDts": "Ignorer DTS (afkodning af tidsstempel)", "IgnoreDts": "Ignorer DTS (afkodning af tidsstempel)",
@ -1711,11 +1711,11 @@
"HeaderConfirmRepositoryInstallation": "Bekræft installation af plugin-repositorium", "HeaderConfirmRepositoryInstallation": "Bekræft installation af plugin-repositorium",
"BackdropScreensaver": "Screensaver baggrund", "BackdropScreensaver": "Screensaver baggrund",
"GetThePlugin": "Få pluginnet", "GetThePlugin": "Få pluginnet",
"AllowSegmentDeletionHelp": "Slet gamle segmenter, når de er blevet sendt til klienten. Dette forhindrer, at man skal gemme hele den transkodede fil på disken. Fungerer kun med throttling aktiveret. Slå dette fra, hvis du oplever afspilningsproblemer.", "AllowSegmentDeletionHelp": "Slet gamle segmenter, når de er blevet hentet af klienten. Dette forhindrer, at man skal gemme hele den transkodede fil på disken. Slå dette fra, hvis du oplever afspilningsproblemer.",
"LabelThrottleDelaySeconds": "Begræns efter", "LabelThrottleDelaySeconds": "Begræns efter",
"LabelThrottleDelaySecondsHelp": "Tid i sekunder, hvorefter transcoderen vil blive begrænset. Skal være stor nok til, at klienten kan opretholde en sund buffer. Virker kun, hvis throttling er aktiveret.", "LabelThrottleDelaySecondsHelp": "Tid i sekunder, hvorefter transcoderen vil blive begrænset. Skal være stor nok til, at klienten kan opretholde en sund buffer. Virker kun, hvis throttling er aktiveret.",
"LabelSegmentKeepSeconds": "Tid at gemme segmenter i", "LabelSegmentKeepSeconds": "Tid at gemme segmenter i",
"LabelSegmentKeepSecondsHelp": "Tid i sekunder, som segmenter skal gemmes i, før de overskrives. Skal være større end \"Begræns efter\". Virker kun, hvis sletning af segmenter er aktiveret.", "LabelSegmentKeepSecondsHelp": "Tid i sekunder, som segmenter skal gemmes i, efter de er hentet af klienten. Virker kun, hvis sletning af segmenter er aktiveret.",
"HeaderGuestCast": "Gæstestjerner", "HeaderGuestCast": "Gæstestjerner",
"LabelAlbumGain": "Albumslydstyrke", "LabelAlbumGain": "Albumslydstyrke",
"LabelSelectAudioNormalization": "Lyd normalisering", "LabelSelectAudioNormalization": "Lyd normalisering",
@ -1757,9 +1757,90 @@
"MediaInfoDvBlSignalCompatibilityId": "DV bl signals kompatibilitets id", "MediaInfoDvBlSignalCompatibilityId": "DV bl signals kompatibilitets id",
"ButtonEditUser": "Rediger bruger", "ButtonEditUser": "Rediger bruger",
"HeaderAllRecordings": "alle optagelser", "HeaderAllRecordings": "alle optagelser",
"ListView": "liste udsigt", "ListView": "Liste Visning",
"LabelVppTonemappingContrastHelp": "Tilføj kontrastere", "LabelVppTonemappingContrastHelp": "Tilføj kontrast gain i VPP tone mapning. Begge anbefalede og standard værdier er 1.",
"LogLevel.Trace": "Spore", "LogLevel.Trace": "Spore",
"LabelServerVersion": "server version", "LabelServerVersion": "server version",
"LabelWebVersion": "Web version" "LabelWebVersion": "Web version",
"AirPlay": "AirPlay",
"PlaybackError.NO_MEDIA_ERROR": "Ikke muligt at finde en gyldig mediekilde at afspille.",
"AllowSubtitleManagement": "Tillad denne bruger at redigere undertekster",
"AllowContentWithTagsHelp": "Vis kun medier med mindst èn af de nævnte tags.",
"LabelUseReplayGainTags": "Brug ReplayGain Tags",
"BlockContentWithTagsHelp": "Gem medier med mindst én af de nævnte tags.",
"ChannelResolutionSD": "SD",
"ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionHD": "HD",
"ChannelResolutionFullHD": "Fuld HD",
"ChannelResolutionUHD4K": "UHD (4K)",
"ConfirmDeleteSeries": "Sletning af denne serie vil slette ALLE {0} afsnit fra både filsystemet og dit mediebibliotek. Er du sikker på du vil fortsætte?",
"DeleteSeries": "Slet Serie",
"DeleteEpisode": "Slet Afsnit",
"DlnaMovedMessage": "DLNA funktionaliteten er flyttet til et plugin.",
"EnableLibrary": "Aktivér biblioteket",
"EnableLibraryHelp": "Deaktivering af biblioteket vil gemme det fra alle brugeres visninger.",
"HeaderDeleteSeries": "Slet Serie",
"LabelAllowContentWithTags": "Tillad elementer med tags",
"LabelBuildVersion": "Build version",
"DeleteName": "Slet {0}",
"DeleteEntireSeries": "Slet {0} Afsnit",
"LabelUseReplayGainTagsHelp": "Skan lydfiler for replaygain tags og brug dem i stedet for at udregne LUFS værdi. (Bruger mindre computerkraft. Vil overskrive 'LUFS Skan' muligheden)",
"PlaybackError.ASS_RENDER_ERROR": "En fejl skete i ASS/SSA undertekst rendereren.",
"PlaybackError.FATAL_HLS_ERROR": "En fatal fejl sekete i HLS strømmen.",
"PlaybackError.MEDIA_DECODE_ERROR": "Afspilning fejlede i afkodning af mediet.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Afspilning fejlede fordi mediet ikke er supporteret af denne klient.",
"PlaybackError.NETWORK_ERROR": "Afspilning fejlede på grund af en netværksfejl.",
"PlaybackError.PLAYER_ERROR": "Afspilning fejlede på grund af en fatal afspiller fejl.",
"PlaybackError.SERVER_ERROR": "Afspilning fejlede på grund af en server fejl.",
"PlaybackError.NotAllowed": "Afspilning af dette medie er ikke tilladt.",
"Lyric": "Lyrik",
"EnableSmoothScroll": "Aktivér glat scroll",
"LimitSupportedVideoResolution": "Begræns maksimal supporteret video opløsning",
"LimitSupportedVideoResolutionHelp": "Brug 'Maksimalle Tilladt Video Transkodning Opløsning' som maksimal supporteret video opløsning.",
"AiTranslated": "AI Oversat",
"LabelEncodingFormatOptions": "Koder format muligheder",
"EncodingFormatHelp": "Vælg den video kodning Jellyfin skal transkode til. Jellyfin vil bruge software kodning når hardware kodning til det valgte format ikke er tilgængelig. H264 kodning vil altid være aktiveret.",
"MachineTranslated": "Maskine Oversat",
"Trickplay": "Trickspil",
"LabelTrickplayAccel": "Aktivér hardware acceleration",
"PlaybackError.RateLimitExceeded": "Dette medie kan ikke afspilles lige nu på grund a rate begrænsninger.",
"LabelImageIntervalHelp": "Tidsinterval (ms) mellem hver ny trickspil billede.",
"EnableVideoToolboxTonemapping": "Aktivér VideoToolbox Tone mapning",
"AllowVideoToolboxTonemappingHelp": "Hardware accelereret tone-mapning fra VideoToolbox. Virker med de fleste HDR formater, inklusiv HDR10, HDR10+, og HLG, men virker ikke med Dolby Vision Profile 5. Denne har højere prioritet end andre Metal implementatitoner.",
"MediaInfoDvVersionMajor": "DV version overordnet",
"MediaInfoDvVersionMinor": "DV version underordnet",
"ForeignPartsOnly": "Kun Tvungne/Udenlandske dele",
"HearingImpairedShort": "HI/SDH",
"LabelIsHearingImpaired": "For hørehæmmede (SDH)",
"AllowMjpegEncoding": "Tillad kodning i MJPEG format (brugt ved trickspil generering)",
"LabelTrickplayAccelHelp": "Vær sikker på at aktivere 'Tillad MJPEG Kodning' i Transkodning hvis dit hardware understøtter det.",
"NonBlockingScan": "Ikke Blokerende - sætter generation i kø, returnerer efterfølgende",
"BlockingScan": "Blokering - sætter generation i kø, blokerer skan indtil færdig",
"LabelScanBehavior": "Skan Opførsel",
"LabelScanBehaviorHelp": "Standard opførsel er ikke blokerende, hvilket vil tilføje medie til biblioteket før trickspil generering gennemføres. Blokering vil sikre art trickspil filer er genereret før mediet tilføjes til biblioteket, men vil gøre så skanning tager væsentlig længere tid.",
"PriorityHigh": "Høj",
"PriorityAboveNormal": "Over Normal",
"PriorityNormal": "Normal",
"PriorityBelowNormal": "Under Normal",
"PriorityIdle": "Stille",
"LabelProcessPriority": "Proces Prioritet",
"LabelProcessPriorityHelp": "Hvis dette sættes højere eller lavere vil definere hvordan CPUen prioriterer ffmpegs trickspils genererings proces i forhold til andre processer. Hvis du oplever langsommelighed mens der genereres trickspil billeder men ikke vil undvære dem, så prøv at indstille dette lidt lavere ligesom tråd antal.",
"LabelImageInterval": "Billede Interval",
"LabelWidthResolutions": "Vidde Opløsninger",
"LabelWidthResolutionsHelp": "Kommasepareret liste over vidder (px) som trickspil billeder genereres i. Alle billeder genereres proportionelt til kilden, så en vidde på 320 på en 16:9 video ender omkring 320x180.",
"LabelTrackGain": "Spor Gain",
"LabelVppTonemappingContrast": "VPP Tone mapning kontrast gain",
"Unknown": "Ukendt",
"AllowAv1Encoding": "Tillad kodning i AV1 format",
"LabelVppTonemappingBrightness": "VPP Tone mapning lysstyrke gain",
"MediaInfoVideoRangeType": "Video rækkevidde type",
"LabelVppTonemappingBrightnessHelp": "Tilføj lysstyrke gain i VPP tone mapning. De anbefalede standard værdier er 16 og 0.",
"VideoRangeTypeNotSupported": "Videoens rækkevidde type er ikke supporteret",
"MediaInfoDvLevel": "DV niveau",
"LabelVideoRangeType": "Video rækkevidde type",
"MediaInfoRpuPresentFlag": "DV rpu forudindstillelses flag",
"MediaInfoElPresentFlag": "DV el forudindstillelses flag",
"MediaInfoBlPresentFlag": "DV bl forudindstillelses flag",
"LabelTonemappingMode": "Tone mapnings metode",
"TonemappingModeHelp": "Vælg tonemapnings metode. Hvis du oplever udsprængte højlys, prøv at skifte til RGB metoden."
} }

View file

@ -200,7 +200,7 @@
"Guide": "Fernsehprogramm", "Guide": "Fernsehprogramm",
"GuideProviderLogin": "Anmelden", "GuideProviderLogin": "Anmelden",
"GuideProviderSelectListings": "Listen wählen", "GuideProviderSelectListings": "Listen wählen",
"H264CrfHelp": "Der Constant Rate Factor (CRF) bezeichnet die Einstellung für die Standardqualität des x264 und x265 Encoders. Setze einen Wert zwischen 0 und 51. Ein niedriger Wert resultiert in besserer Qualität (auf Kosten einer größeren Datei). Gängige Werte sind 18-28. Der Standard für x264 ist 23 und x265 ist 28, diese sollten als Referenzen verwendet werden.", "H264CrfHelp": "Der Constant Rate Factor (CRF) bezeichnet die Einstellung für die Standardqualität des x264 und x265 Software-Encoders. Setze einen Wert zwischen 0 und 51. Ein niedriger Wert resultiert in besserer Qualität (auf Kosten einer größeren Datei). Gängige Werte sind 18-28. Der Standard für x264 ist 23 und 28 für x265, diese sollten als Referenzen verwendet werden. Hardware-Encoder verwenden diese Einstellung nicht.",
"EncoderPresetHelp": "Wähle einen schnelleren Wert um die Performance zu verbessern oder einen langsameren Wert um die Qualität zu verbessern.", "EncoderPresetHelp": "Wähle einen schnelleren Wert um die Performance zu verbessern oder einen langsameren Wert um die Qualität zu verbessern.",
"HDPrograms": "HD Programme", "HDPrograms": "HD Programme",
"HardwareAccelerationWarning": "Das Aktivieren der Hardwarebeschleunigung kann auf einigen Systemen zu Instabilität führen. Stelle sicher, dass das Betriebssystem sowie die Grafikkarten-Treiber auf dem aktuellsten Stand sind. Wenn nach der Aktivierung Probleme mit der Wiedergabe von Videos auftreten, stelle diese Einstellung zurück auf \"Keine\".", "HardwareAccelerationWarning": "Das Aktivieren der Hardwarebeschleunigung kann auf einigen Systemen zu Instabilität führen. Stelle sicher, dass das Betriebssystem sowie die Grafikkarten-Treiber auf dem aktuellsten Stand sind. Wenn nach der Aktivierung Probleme mit der Wiedergabe von Videos auftreten, stelle diese Einstellung zurück auf \"Keine\".",
@ -1377,7 +1377,7 @@
"LabelColorSpace": "Farbraum", "LabelColorSpace": "Farbraum",
"MediaInfoColorSpace": "Farbraum", "MediaInfoColorSpace": "Farbraum",
"VideoAudio": "Videoton", "VideoAudio": "Videoton",
"AllowTonemappingHelp": "Tone-Mapping kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei 10bit HDR10, HLG und Dolby-Vision Videos und benötigt die entsprechende OpenCL- oder CUDA-Laufzeitumgebung.", "AllowTonemappingHelp": "Tone-Mapping kann den Dynamikumfang eines Videos von HDR nach SDR wandeln und dabei die für die Darstellung der Originalszene sehr wichtigen Bilddetails und Farben beibehalten. Dies funktioniert zurzeit nur bei 10bit HDR10, HLG und Dolby-Vision Videos und benötigt die entsprechende GPGPU-Laufzeitumgebung.",
"TonemappingRangeHelp": "Wähle den Ausgabefarbraum aus. Auto ist derselbe wie der Eingabefarbraum.", "TonemappingRangeHelp": "Wähle den Ausgabefarbraum aus. Auto ist derselbe wie der Eingabefarbraum.",
"TonemappingAlgorithmHelp": "Das Tone-Mapping kann fein abgestimmt werden. Wenn du mit diesen Optionen nicht vertraut bist, behalte einfach den Standardwert bei. Der empfohlene Wert ist \"BT.2390\".", "TonemappingAlgorithmHelp": "Das Tone-Mapping kann fein abgestimmt werden. Wenn du mit diesen Optionen nicht vertraut bist, behalte einfach den Standardwert bei. Der empfohlene Wert ist \"BT.2390\".",
"LabelTonemappingAlgorithm": "Wähle den zu verwendenden Tone-Mapping-Algorithmus aus", "LabelTonemappingAlgorithm": "Wähle den zu verwendenden Tone-Mapping-Algorithmus aus",
@ -1649,7 +1649,7 @@
"MessageUnauthorizedUser": "Du bist aktuell nicht berechtigt, auf den Server zuzugreifen. Bitte kontaktiere den Serveradministrator für weitere Informationen.", "MessageUnauthorizedUser": "Du bist aktuell nicht berechtigt, auf den Server zuzugreifen. Bitte kontaktiere den Serveradministrator für weitere Informationen.",
"StoryArc": "Handlungsstrang", "StoryArc": "Handlungsstrang",
"Digital": "Digital", "Digital": "Digital",
"EnableEnhancedNvdecDecoderHelp": "Experimentelle NVDEC-Implementierung, nur aktiviere, falls es zu Dekodierungsfehlern kommt.", "EnableEnhancedNvdecDecoderHelp": "Verbesserte NVDEC-Implementierung, deaktivieren, falls es zu Dekodierungsfehlern kommt.",
"HomeVideosPhotos": "Heimvideos und -bilder", "HomeVideosPhotos": "Heimvideos und -bilder",
"Bold": "Fett", "Bold": "Fett",
"LabelTextWeight": "Schriftstärke", "LabelTextWeight": "Schriftstärke",
@ -1798,5 +1798,74 @@
"ChannelResolutionFullHD": "Full HD", "ChannelResolutionFullHD": "Full HD",
"ChannelResolutionUHD4K": "UHD (4K)", "ChannelResolutionUHD4K": "UHD (4K)",
"EnableVideoToolboxTonemapping": "VideoToolbox-Tone-Mapping aktivieren", "EnableVideoToolboxTonemapping": "VideoToolbox-Tone-Mapping aktivieren",
"AllowVideoToolboxTonemappingHelp": "Hardwarebeschleunigtes Tone-Mapping welches von VideoToolbox zur verfügung gestellt wird. Es funktioniert mit den meisten HDR Formaten wie HDR10, HDR10+ und HLG, aber es funktioniert nicht mit Dolby Vision Profile 5. Dies hat eine höhere Priorität verglichen mit einer anderen OpenCL implementation." "AllowVideoToolboxTonemappingHelp": "Hardwarebeschleunigtes Tone-Mapping, welches von VideoToolbox zur Verfügung gestellt wird. Es funktioniert mit den meisten HDR-Formaten wie HDR10, HDR10+ und HLG, aber es funktioniert nicht mit Dolby Vision Profile 5. Dies hat eine höhere Priorität, verglichen mit einer anderen Metal Implementation.",
"AirPlay": "AirPlay",
"AllowContentWithTagsHelp": "Nur Medien mit mindestens einem der spezifizierten Tags anzeigen.",
"BlockContentWithTagsHelp": "Medien mit mindestens einem der spezifizierten Tags verstecken.",
"EnableLibrary": "Aktiviert die Bibliothek",
"EnableLibraryHelp": "Mit dem deaktivieren der Bibliothek ist sie für keinen Benutzer mehr sichtbar.",
"PlaybackError.ASS_RENDER_ERROR": "Beim rendern der ASS/SSA Untertitel ist ein Fehler aufgetreten.",
"ConfirmDeleteSeries": "Das löschen dieser Serie wird alle {0} Folgen aus deiner Medienbibliothek und dem Dateisystem löschen. Bist du sicher das du fortfahren möchtest?",
"DeleteEntireSeries": "Lösche {0} Folgen",
"DeleteSeries": "Lösche Serien",
"DeleteEpisode": "Lösche Folge",
"HeaderDeleteSeries": "Lösche Serien",
"LabelAllowContentWithTags": "Erlaube Einträge mit Tags",
"Lyric": "Songtext",
"LimitSupportedVideoResolution": "Limitiert die maximale Videoauflösung",
"LabelEncodingFormatOptions": "Codierungsformat-Optionen",
"EncodingFormatHelp": "Wähl die Videokodierung, in die Jellyfin transcodieren soll. Jellyfin wird die Softwarecodierung verwenden, wenn die Hardwarebeschleunigung für das ausgewählte Format nicht verfügbar ist. Die H264-Codierung wird immer aktiviert sein.",
"LimitSupportedVideoResolutionHelp": "Benutze \"Maximal erlaubte Auflösung der Videotranskodierung\" als maximal unterstützte Videoauflösung.",
"AllowMjpegEncoding": "Codierung im MJPEG-Format zulassen (wird bei der Trickplay-Erstellung verwendet)",
"Trickplay": "Trickplay",
"LabelTrickplayAccel": "Hardware-Dekoder aktivieren",
"LabelTrickplayAccelHelp": "Sicherstellen, dass die Option \"MJPEG-Codierung zulassen\" unter \"Transcodierung\" aktiviert ist, wenn die Hardware dies unterstützt.",
"LabelScanBehavior": "Scanverhalten",
"PriorityHigh": "Hoch",
"LabelScanBehaviorHelp": "Die Standardeinstellung ist \"nicht blockierend\", d. h., die Medien werden der Bibliothek hinzugefügt, bevor das Trickplay erzeugt wird. Durch die Blockierung wird sichergestellt, dass Trickplay-Dateien generiert werden, bevor Medien zur Bibliothek hinzugefügt werden, aber die Suchvorgänge dauern erheblich länger.",
"PriorityAboveNormal": "Über Normal",
"PriorityNormal": "Normal",
"PriorityBelowNormal": "Unter Normal",
"PriorityIdle": "Leerlauf",
"LabelProcessPriority": "Prozess-Priorität",
"LabelImageInterval": "Bildintervall",
"LabelWidthResolutions": "Breitenauflösung",
"LabelWidthResolutionsHelp": "Durch Kommata getrennte Liste der Breiten (px), in denen Trickplay-Bilder generiert werden sollen. Alle Bilder sollten proportional zur Quelle generiert werden, d.h. eine Breite von 320 bei einem 16:9-Video ergibt etwa 320x180.",
"LabelProcessPriorityHelp": "Eine niedrigere oder höhere Einstellung legt fest, wie die CPU den ffmpeg-Trickplay-Erzeugungsprozess im Verhältnis zu anderen Prozessen priorisiert. Wenn die Erzeugung von Trickplay-Bildern verlangsamt ist, die Erzeugung aber nicht vollständig gestoppt werden soll, versuchen, diesen Wert sowie die Anzahl der Threads zu verringern.",
"LabelTileWidth": "Kachelbreite",
"LabelTileWidthHelp": "Maximale Anzahl von Bildern pro Kachel in X-Richtung.",
"LabelTileHeight": "Kachelhöhe",
"LabelTileHeightHelp": "Maximale Anzahl von Bildern pro Kachel in Y-Richtung.",
"LabelJpegQuality": "JPEG Qualität",
"LabelJpegQualityHelp": "Die JPEG-Komprimierungsqualität für Trickplay-Bilder.",
"LabelQscale": "Qscale",
"LabelQscaleHelp": "Die Qualitätsskala der von ffmpeg ausgegebenen Bilder, wobei 2 die höchste und 31 die niedrigste Qualität darstellt.",
"ExtractTrickplayImagesHelp": "Trickplay-Bilder ähneln den Kapitelbildern, mit dem Unterschied, dass sie sich über die gesamte Länge des Inhalts erstrecken und beim Scrubbing von Videos als Vorschau angezeigt werden.",
"LabelImageIntervalHelp": "Zeitintervall (ms) zwischen jedem neuen Trickplay Bild.",
"LabelTrickplayThreads": "FFmpeg Threads",
"LabelTrickplayThreadsHelp": "Die Anzahl der Threads, die an das Argument '-threads' von ffmpeg übergeben wird.",
"OptionExtractTrickplayImage": "Extrahieren von Trickplay-Bildern aktivieren",
"LabelExtractTrickplayDuringLibraryScan": "Extrahieren von Trickplay-Bildern beim Scannen der Bibliothek",
"LabelExtractTrickplayDuringLibraryScanHelp": "Trickplay-Bilder erzeugen, wenn Videos während der Bibliothekssuche importiert werden. Andernfalls werden sie während der geplanten Aufgabe \"Trickplay-Bilder\" extrahiert. Wenn die Generierung auf nicht blockierend eingestellt ist, hat dies keinen Einfluss auf die Zeit, die eine Bibliotheksüberprüfung dauert.",
"EnableSmoothScroll": "Flüssiges Scrollen aktivieren",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Die Wiedergabe ist fehlgeschlagen, weil die Medien von diesem Client nicht unterstützt wird.",
"PlaybackError.FATAL_HLS_ERROR": "Im HLS-Stream ist ein schwerwiegender Fehler aufgetreten.",
"PlaybackError.MEDIA_DECODE_ERROR": "Die Wiedergabe ist aufgrund eines Fehlers bei der Dekodierung der Medien fehlgeschlagen.",
"PlaybackError.NETWORK_ERROR": "Die Wiedergabe ist aufgrund eines Netzwerkfehlers fehlgeschlagen.",
"PlaybackError.NO_MEDIA_ERROR": "Es konnte keine gültige Medienquelle für die Wiedergabe gefunden werden.",
"PlaybackError.SERVER_ERROR": "Die Wiedergabe ist aufgrund eines Serverfehlers fehlgeschlagen.",
"PlaybackError.NotAllowed": "Die Wiedergabe dieser Medien ist nicht erlaubt.",
"PlaybackError.RateLimitExceeded": "Dieses Medium kann derzeit aufgrund von Beschränkungen nicht abgespielt werden.",
"PlaybackError.PLAYER_ERROR": "Die Wiedergabe ist aufgrund eines schwerwiegenden Player-Fehlers fehlgeschlagen.",
"NonBlockingScan": "Non Blocking - reiht Erstellung ein, kehrt dann zurück",
"BlockingScan": "Blocking - reiht Erstellung ein, blockiert Scan bis fertig",
"LabelTrickplayAccelEncoding": "Hardwarebeschleunigte MJPEG-Enkodierung aktivieren",
"LabelTrickplayAccelEncodingHelp": "Momentan nur für QSV und VAAPI verfügbar. Diese Option hat für andere Methoden keine Auswirkungen.",
"Lyrics": "Songtexte",
"ConfirmDeleteLyrics": "Das Löschen dieses Songtextes löscht die Datei vom Laufwerk und in deiner Medienbibliothek. Bist du wirklich sicher?",
"DeleteLyrics": "Songtexte löschen",
"ErrorDeletingLyrics": "Es trat ein Fehler beim Löschen der Songtexte vom Server auf. Bitte stelle sicher, dass Jellyfin die notwendigen Zugriffsrechte besitzt.",
"HeaderDeleteLyrics": "Songtexte löschen",
"HeaderNoLyrics": "Keine Songtexte gefunden",
"ViewLyrics": "Songtexte ansehen"
} }

View file

@ -254,7 +254,7 @@
"Guide": "Guide", "Guide": "Guide",
"GuideProviderLogin": "Login", "GuideProviderLogin": "Login",
"GuideProviderSelectListings": "Select Listings", "GuideProviderSelectListings": "Select Listings",
"H264CrfHelp": "The 'Constant Rate Factor' (CRF) is the default quality setting for the x264 and x265 encoder. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, and for x265 is 28, so you can use this as a starting point.", "H264CrfHelp": "The 'Constant Rate Factor' (CRF) is the default quality setting for the x264 and x265 software encoders. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, and for x265 is 28, so you can use this as a starting point. Hardware encoders do not use these settings.",
"EncoderPresetHelp": "Choose a faster value to improve performance, or a slower value to improve quality.", "EncoderPresetHelp": "Choose a faster value to improve performance, or a slower value to improve quality.",
"HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.", "HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.",
"HeaderAccessSchedule": "Access Schedule", "HeaderAccessSchedule": "Access Schedule",
@ -1392,7 +1392,7 @@
"LabelTonemappingRange": "Tone mapping range", "LabelTonemappingRange": "Tone mapping range",
"TonemappingAlgorithmHelp": "Tone mapping can be fine-tuned. If you are not familiar with these options, just keep the default. The recommended value is 'BT.2390'.", "TonemappingAlgorithmHelp": "Tone mapping can be fine-tuned. If you are not familiar with these options, just keep the default. The recommended value is 'BT.2390'.",
"LabelTonemappingAlgorithm": "Select the Tone mapping algorithm to use", "LabelTonemappingAlgorithm": "Select the Tone mapping algorithm to use",
"AllowTonemappingHelp": "Tone mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colours, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.", "AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colours, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding GPGPU runtime.",
"EnableTonemapping": "Enable Tone mapping", "EnableTonemapping": "Enable Tone mapping",
"LabelOpenclDeviceHelp": "This is the OpenCL device that is used for tone mapping. The left side of the dot is the platform number, and the right side is the device number on the platform. The default value is 0.0. The FFmpeg application file containing the OpenCL hardware acceleration method is required.", "LabelOpenclDeviceHelp": "This is the OpenCL device that is used for tone mapping. The left side of the dot is the platform number, and the right side is the device number on the platform. The default value is 0.0. The FFmpeg application file containing the OpenCL hardware acceleration method is required.",
"LabelOpenclDevice": "OpenCL Device", "LabelOpenclDevice": "OpenCL Device",
@ -1650,7 +1650,7 @@
"OriginalAirDate": "Original Air Date", "OriginalAirDate": "Original Air Date",
"Digital": "Digital", "Digital": "Digital",
"MessageUnauthorizedUser": "You are not authorized to access the server at this time. Please contact your server administrator for more information.", "MessageUnauthorizedUser": "You are not authorized to access the server at this time. Please contact your server administrator for more information.",
"EnableEnhancedNvdecDecoderHelp": "Experimental NVDEC implementation, do not enable this option unless you encounter decoding errors.", "EnableEnhancedNvdecDecoderHelp": "Enhanced NVDEC implementation, disable this option to use CUVID if you encounter decoding errors.",
"EnableSplashScreen": "Enable the splash screen", "EnableSplashScreen": "Enable the splash screen",
"Bold": "Bold", "Bold": "Bold",
"HomeVideosPhotos": "Home Videos and Photos", "HomeVideosPhotos": "Home Videos and Photos",
@ -1757,11 +1757,11 @@
"LogLevel.None": "None", "LogLevel.None": "None",
"HeaderEpisodesStatus": "Episodes Status", "HeaderEpisodesStatus": "Episodes Status",
"AllowSegmentDeletion": "Delete segments", "AllowSegmentDeletion": "Delete segments",
"AllowSegmentDeletionHelp": "Delete old segments after they have been sent to the client. This prevents having to store the entire transcoded file on disk. Will only work with throttling enabled. Turn this off if you experience playback issues.", "AllowSegmentDeletionHelp": "Delete old segments after they have been downloaded by the client. This prevents having to store the entire transcoded file on disk. Turn this off if you experience playback issues.",
"LabelThrottleDelaySeconds": "Throttle after", "LabelThrottleDelaySeconds": "Throttle after",
"LabelThrottleDelaySecondsHelp": "Time in seconds after which the transcoder will be throttled. Must be large enough for the client to maintain a healthy buffer. Only works if throttling is enabled.", "LabelThrottleDelaySecondsHelp": "Time in seconds after which the transcoder will be throttled. Must be large enough for the client to maintain a healthy buffer. Only works if throttling is enabled.",
"LabelSegmentKeepSeconds": "Time to keep segments", "LabelSegmentKeepSeconds": "Time to keep segments",
"LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept before they are overwritten. Must be greater than \"Throttle after\". Only works if segment deletion is enabled.", "LabelSegmentKeepSecondsHelp": "Time in seconds for which segments should be kept after they are downloaded by the client. Only works if segment deletion is enabled.",
"AllowAv1Encoding": "Allow encoding in AV1 format", "AllowAv1Encoding": "Allow encoding in AV1 format",
"GoHome": "Go Home", "GoHome": "Go Home",
"UnknownError": "An unknown error occurred.", "UnknownError": "An unknown error occurred.",
@ -1786,5 +1786,79 @@
"ButtonEditUser": "Edit user", "ButtonEditUser": "Edit user",
"LabelBuildVersion": "Build version", "LabelBuildVersion": "Build version",
"LabelServerVersion": "Server version", "LabelServerVersion": "Server version",
"LabelWebVersion": "Web version" "LabelWebVersion": "Web version",
"EnableVideoToolboxTonemapping": "Enable VideoToolbox Tone mapping",
"AllowMjpegEncoding": "Allow encoding in MJPEG format (used during trickplay generation)",
"Trickplay": "Trickplay",
"LabelTrickplayAccel": "Enable hardware decoding",
"LabelTrickplayAccelHelp": "Make sure to enable 'Allow MJPEG Encoding' in Transcoding if your hardware supports it.",
"NonBlockingScan": "Non-blocking - queues generation, then returns",
"BlockingScan": "Blocking - queues generation, blocks scan until complete",
"LabelScanBehavior": "Scan Behavior",
"PriorityHigh": "High",
"PriorityAboveNormal": "Above Normal",
"LabelImageInterval": "Image Interval",
"LabelTileWidthHelp": "Maximum number of images per tile in the X direction.",
"LabelTileWidth": "Tile Width",
"LabelQscaleHelp": "The quality scale of images output by ffmpeg, with 2 being the highest quality and 31 being the lowest.",
"LabelTileHeightHelp": "Maximum number of images per tile in the Y direction.",
"LabelJpegQuality": "JPEG Quality",
"LabelJpegQualityHelp": "The JPEG compression quality for trickplay images.",
"LabelQscale": "Qscale",
"ExtractTrickplayImagesHelp": "Trickplay images are similar to chapter images, except they span the entire length of the content and are used to show a preview when scrubbing through videos.",
"ChannelResolutionHD": "HD",
"ChannelResolutionFullHD": "Full HD",
"ChannelResolutionSD": "SD",
"ChannelResolutionUHD4K": "UHD (4K)",
"ChannelResolutionSDPAL": "SD (PAL)",
"ConfirmDeleteSeries": "Deleting this show will delete ALL {0} episodes from both the file system and your media library. Are you sure you wish to continue?",
"DeleteEntireSeries": "Delete {0} Episodes",
"DeleteSeries": "Delete Show",
"DeleteEpisode": "Delete Episode",
"HeaderDeleteSeries": "Delete Show",
"AllowVideoToolboxTonemappingHelp": "Hardware-accelerated tone-mapping provided by VideoToolbox. It works with most HDR formats, including HDR10, HDR10+, and HLG, but does not work with Dolby Vision Profile 5. This has a higher priority compared to another Metal implementation.",
"PriorityNormal": "Normal",
"LabelScanBehaviorHelp": "The default behavior is non-blocking, which will add media to the library before trickplay generation is done. Blocking will ensure trickplay files are generated before media is added to the library, but will make scans significantly longer.",
"PriorityBelowNormal": "Below Normal",
"PriorityIdle": "Idle",
"LabelProcessPriority": "Process Priority",
"LabelProcessPriorityHelp": "Setting this lower or higher will determine how the CPU prioritizes the ffmpeg trickplay generation process in relation to other processes. If you notice slowdown while generating trickplay images but don't want to fully stop their generation, try lowering this as well as the thread count.",
"LabelImageIntervalHelp": "Interval of time (ms) between each new trickplay image.",
"LabelWidthResolutions": "Width Resolutions",
"LabelWidthResolutionsHelp": "Comma separated list of the widths (px) that trickplay images will be generated at. All images should generate proportionally to the source, so a width of 320 on a 16:9 video ends up around 320x180.",
"LabelTileHeight": "Tile Height",
"LabelTrickplayThreads": "FFmpeg Threads",
"LabelTrickplayThreadsHelp": "The number of threads to pass to the '-threads' argument of ffmpeg.",
"OptionExtractTrickplayImage": "Enable trickplay image extraction",
"LabelExtractTrickplayDuringLibraryScan": "Extract trickplay images during the library scan",
"LabelExtractTrickplayDuringLibraryScanHelp": "Generate trickplay images when videos are imported during the library scan. Otherwise, they will be extracted during the trickplay images scheduled task. If generation is set to non-blocking this will not affect the time a library scan takes to complete.",
"DeleteName": "Delete {0}",
"LabelUseReplayGainTagsHelp": "Scan audio files for replaygain tags and use them instead of computing LUFS value. (Uses less computing power. Will override 'LUFS Scan' option)",
"LabelUseReplayGainTags": "Use ReplayGain Tags",
"AllowSubtitleManagement": "Allow this user to edit subtitles",
"DlnaMovedMessage": "The DLNA functionality has moved to a plugin.",
"EnableLibrary": "Enable the library",
"EnableLibraryHelp": "Disabling the library will hide it from all user views.",
"LimitSupportedVideoResolution": "Limit maximum supported video resolution",
"AirPlay": "AirPlay",
"AllowContentWithTagsHelp": "Only show media with at least one of the specified tags.",
"BlockContentWithTagsHelp": "Hide media with at least one of the specified tags.",
"LabelAllowContentWithTags": "Allow items with tags",
"EnableSmoothScroll": "Enable smooth scroll",
"EncodingFormatHelp": "Select the video encoding that Jellyfin should transcode to. Jellyfin will use software encoding when hardware acceleration for the selected format is not available. H264 encoding will always be enabled.",
"LabelTrickplayAccelEncodingHelp": "Currently only available on QSV and VAAPI, this option has no effect on other hardware acceleration methods.",
"LabelEncodingFormatOptions": "Encoding format options",
"LabelTrickplayAccelEncoding": "Enable hardware accelerated MJPEG encoding",
"LimitSupportedVideoResolutionHelp": "Use 'Maximum Allowed Video Transcoding Resolution' as maximum supported video resolution.",
"PlaybackError.ASS_RENDER_ERROR": "An error was encountered in the ASS/SSA subtitle renderer.",
"PlaybackError.FATAL_HLS_ERROR": "A fatal error was encountered in the HLS stream.",
"PlaybackError.MEDIA_DECODE_ERROR": "Playback failed due to an error decoding the media.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Playback failed because the media is not supported by this client.",
"PlaybackError.NETWORK_ERROR": "Playback failed due to a network error.",
"PlaybackError.NO_MEDIA_ERROR": "Unable to find a valid media source to play.",
"PlaybackError.PLAYER_ERROR": "Playback failed due to a fatal player error.",
"PlaybackError.SERVER_ERROR": "Playback failed due to a server error.",
"PlaybackError.NotAllowed": "Playback of this media is not allowed.",
"PlaybackError.RateLimitExceeded": "This media cannot be played at this time due to rate limits.",
"Lyric": "Lyric"
} }

View file

@ -10,6 +10,7 @@
"AddToPlayQueue": "Add to play queue", "AddToPlayQueue": "Add to play queue",
"AgeValue": "({0} years old)", "AgeValue": "({0} years old)",
"AirDate": "Air date", "AirDate": "Air date",
"AirPlay": "AirPlay",
"Aired": "Aired", "Aired": "Aired",
"Album": "Album", "Album": "Album",
"AlbumArtist": "Album Artist", "AlbumArtist": "Album Artist",
@ -23,6 +24,7 @@
"AllLibraries": "All libraries", "AllLibraries": "All libraries",
"AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.", "AllowedRemoteAddressesHelp": "Comma separated list of IP addresses or IP/netmask entries for networks that will be allowed to connect remotely. If left blank, all remote addresses will be allowed.",
"AllowCollectionManagement": "Allow this user to manage collections", "AllowCollectionManagement": "Allow this user to manage collections",
"AllowContentWithTagsHelp": "Only show media with at least one of the specified tags.",
"AllowSubtitleManagement": "Allow this user to edit subtitles", "AllowSubtitleManagement": "Allow this user to edit subtitles",
"AllowFfmpegThrottling": "Throttle Transcodes", "AllowFfmpegThrottling": "Throttle Transcodes",
"AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.", "AllowFfmpegThrottlingHelp": "When a transcode or remux gets far enough ahead from the current playback position, pause the process so it will consume less resources. This is most useful when watching without seeking often. Turn this off if you experience playback issues.",
@ -39,7 +41,7 @@
"AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.", "AllowOnTheFlySubtitleExtractionHelp": "Embedded subtitles can be extracted from videos and delivered to clients in plain text, in order to help prevent video transcoding. On some systems this can take a long time and cause video playback to stall during the extraction process. Disable this to have embedded subtitles burned in with video transcoding when they are not natively supported by the client device.",
"AllowRemoteAccess": "Allow remote connections to this server", "AllowRemoteAccess": "Allow remote connections to this server",
"AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.", "AllowRemoteAccessHelp": "If unchecked, all remote connections will be blocked.",
"AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.", "AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding GPGPU runtime.",
"AlwaysPlaySubtitles": "Always Play", "AlwaysPlaySubtitles": "Always Play",
"AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.", "AlwaysPlaySubtitlesHelp": "Subtitles matching the language preference will be loaded regardless of the audio language.",
"AnyLanguage": "Any Language", "AnyLanguage": "Any Language",
@ -66,6 +68,7 @@
"BirthLocation": "Birth location", "BirthLocation": "Birth location",
"BirthPlaceValue": "Birth place: {0}", "BirthPlaceValue": "Birth place: {0}",
"Blacklist": "Blacklist", "Blacklist": "Blacklist",
"BlockContentWithTagsHelp": "Hide media with at least one of the specified tags.",
"BookLibraryHelp": "Audio and text books are supported. Review the {0} book naming guide {1}.", "BookLibraryHelp": "Audio and text books are supported. Review the {0} book naming guide {1}.",
"Books": "Books", "Books": "Books",
"Box": "Box", "Box": "Box",
@ -157,7 +160,9 @@
"ConfigureDateAdded": "Set up how metadata for 'Date added' is determined in the Dashboard > Libraries > NFO Settings", "ConfigureDateAdded": "Set up how metadata for 'Date added' is determined in the Dashboard > Libraries > NFO Settings",
"ConfirmDeleteImage": "Delete image?", "ConfirmDeleteImage": "Delete image?",
"ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?", "ConfirmDeleteItem": "Deleting this item will delete it from both the file system and your media library. Are you sure you wish to continue?",
"ConfirmDeleteSeries": "Deleting this series will delete ALL {0} episodes from both the file system and your media library. Are you sure you wish to continue?",
"ConfirmDeleteItems": "Deleting these items will delete them from both the file system and your media library. Are you sure you wish to continue?", "ConfirmDeleteItems": "Deleting these items will delete them from both the file system and your media library. Are you sure you wish to continue?",
"ConfirmDeleteLyrics": "Deleting these lyrics will delete them from both the file system and your media library. Are you sure you wish to continue?",
"ConfirmDeletion": "Confirm Deletion", "ConfirmDeletion": "Confirm Deletion",
"ConfirmEndPlayerSession": "Would you like to shutdown Jellyfin on {0}?", "ConfirmEndPlayerSession": "Would you like to shutdown Jellyfin on {0}?",
"Connect": "Connect", "Connect": "Connect",
@ -181,12 +186,16 @@
"DefaultSubtitlesHelp": "Subtitles are loaded based on the default and forced flags in the embedded metadata. Language preferences are considered when multiple options are available.", "DefaultSubtitlesHelp": "Subtitles are loaded based on the default and forced flags in the embedded metadata. Language preferences are considered when multiple options are available.",
"DeinterlaceMethodHelp": "Select the deinterlacing method to use when software transcoding interlaced content. When hardware acceleration supporting hardware deinterlacing is enabled the hardware deinterlacer will be used instead of this setting.", "DeinterlaceMethodHelp": "Select the deinterlacing method to use when software transcoding interlaced content. When hardware acceleration supporting hardware deinterlacing is enabled the hardware deinterlacer will be used instead of this setting.",
"Delete": "Delete", "Delete": "Delete",
"DeleteEntireSeries": "Delete {0} Episodes",
"DeleteAll": "Delete All", "DeleteAll": "Delete All",
"DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.", "DeleteDeviceConfirmation": "Are you sure you wish to delete this device? It will reappear the next time a user signs in with it.",
"DeleteDevicesConfirmation": "Are you sure you wish to delete all devices? All other sessions will be logged out. Devices will reappear the next time a user signs in.", "DeleteDevicesConfirmation": "Are you sure you wish to delete all devices? All other sessions will be logged out. Devices will reappear the next time a user signs in.",
"DeleteImage": "Delete Image", "DeleteImage": "Delete Image",
"DeleteImageConfirmation": "Are you sure you wish to delete this image?", "DeleteImageConfirmation": "Are you sure you wish to delete this image?",
"DeleteLyrics": "Delete lyrics",
"DeleteMedia": "Delete media", "DeleteMedia": "Delete media",
"DeleteSeries": "Delete Series",
"DeleteEpisode": "Delete Episode",
"DeleteName": "Delete {0}", "DeleteName": "Delete {0}",
"DeleteUser": "Delete User", "DeleteUser": "Delete User",
"DeleteUserConfirmation": "Are you sure you wish to delete this user?", "DeleteUserConfirmation": "Are you sure you wish to delete this user?",
@ -242,6 +251,8 @@
"EnableFasterAnimations": "Faster animations", "EnableFasterAnimations": "Faster animations",
"EnableFasterAnimationsHelp": "Use faster animations and transitions.", "EnableFasterAnimationsHelp": "Use faster animations and transitions.",
"EnableHardwareEncoding": "Enable hardware encoding", "EnableHardwareEncoding": "Enable hardware encoding",
"EnableLibrary": "Enable the library",
"EnableLibraryHelp": "Disabling the library will hide it from all user views.",
"EnableNextVideoInfoOverlay": "Show next video info during playback", "EnableNextVideoInfoOverlay": "Show next video info during playback",
"EnableNextVideoInfoOverlayHelp": "At the end of a video, display info about the next video coming up in the current playlist.", "EnableNextVideoInfoOverlayHelp": "At the end of a video, display info about the next video coming up in the current playlist.",
"EnablePhotos": "Display the photos", "EnablePhotos": "Display the photos",
@ -250,6 +261,7 @@
"EnableRewatchingNextUp": "Enable Rewatching in Next Up", "EnableRewatchingNextUp": "Enable Rewatching in Next Up",
"EnableRewatchingNextUpHelp": "Enable showing already watched episodes in 'Next Up' sections.", "EnableRewatchingNextUpHelp": "Enable showing already watched episodes in 'Next Up' sections.",
"EnableQuickConnect": "Enable Quick Connect on this server", "EnableQuickConnect": "Enable Quick Connect on this server",
"EnableSmoothScroll": "Enable smooth scroll",
"EnableStreamLooping": "Auto-loop live streams", "EnableStreamLooping": "Auto-loop live streams",
"EnableStreamLoopingHelp": "Enable this if live streams only contain a few seconds of data and need to be continuously requested. Enabling this when not needed may cause problems.", "EnableStreamLoopingHelp": "Enable this if live streams only contain a few seconds of data and need to be continuously requested. Enabling this when not needed may cause problems.",
"EnableThemeSongsHelp": "Play the theme songs in background while browsing the library.", "EnableThemeSongsHelp": "Play the theme songs in background while browsing the library.",
@ -267,6 +279,7 @@
"ErrorAddingXmlTvFile": "There was an error accessing the XMLTV file. Please ensure the file exists and try again.", "ErrorAddingXmlTvFile": "There was an error accessing the XMLTV file. Please ensure the file exists and try again.",
"ErrorDefault": "There was an error processing the request. Please try again later.", "ErrorDefault": "There was an error processing the request. Please try again later.",
"ErrorDeletingItem": "There was an error deleting the item from the server. Please check that Jellyfin has write access to the media folder and try again.", "ErrorDeletingItem": "There was an error deleting the item from the server. Please check that Jellyfin has write access to the media folder and try again.",
"ErrorDeletingLyrics": "There was an error deleting the lyrics from the server. Please check that Jellyfin has write access to the media folder and try again.",
"ErrorGettingTvLineups": "There was an error downloading TV lineups. Please ensure your information is correct and try again.", "ErrorGettingTvLineups": "There was an error downloading TV lineups. Please ensure your information is correct and try again.",
"ErrorPlayerNotFound": "No player found for the requested media.", "ErrorPlayerNotFound": "No player found for the requested media.",
"ErrorPleaseSelectLineup": "Please select a lineup and try again. If no lineups are available, then please check that your username, password, and postal code is correct.", "ErrorPleaseSelectLineup": "Please select a lineup and try again. If no lineups are available, then please check that your username, password, and postal code is correct.",
@ -311,7 +324,7 @@
"Guide": "Guide", "Guide": "Guide",
"GuideProviderLogin": "Login", "GuideProviderLogin": "Login",
"GuideProviderSelectListings": "Select Listings", "GuideProviderSelectListings": "Select Listings",
"H264CrfHelp": "The 'Constant Rate Factor' (CRF) is the default quality setting for the x264 and x265 encoder. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, and for x265 is 28, so you can use this as a starting point.", "H264CrfHelp": "The 'Constant Rate Factor' (CRF) is the default quality setting for the x264 and x265 software encoders. You can set the values between 0 and 51, where lower values would result in better quality (at the expense of higher file sizes). Sane values are between 18 and 28. The default for x264 is 23, and for x265 is 28, so you can use this as a starting point. Hardware encoders do not use these settings.",
"HDPrograms": "HD programs", "HDPrograms": "HD programs",
"HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.", "HardwareAccelerationWarning": "Enabling hardware acceleration may cause instability in some environments. Ensure that your operating system and video drivers are fully up to date. If you have difficulty playing video after enabling this, you'll need to change the setting back to None.",
"HeaderAccessSchedule": "Access Schedule", "HeaderAccessSchedule": "Access Schedule",
@ -359,7 +372,9 @@
"HeaderDeleteDevice": "Delete Device", "HeaderDeleteDevice": "Delete Device",
"HeaderDeleteDevices": "Delete All Devices", "HeaderDeleteDevices": "Delete All Devices",
"HeaderDeleteItem": "Delete Item", "HeaderDeleteItem": "Delete Item",
"HeaderDeleteSeries": "Delete Series",
"HeaderDeleteItems": "Delete Items", "HeaderDeleteItems": "Delete Items",
"HeaderDeleteLyrics": "Delete Lyrics",
"HeaderDeleteProvider": "Delete Provider", "HeaderDeleteProvider": "Delete Provider",
"HeaderDeleteTaskTrigger": "Delete Task Trigger", "HeaderDeleteTaskTrigger": "Delete Task Trigger",
"HeaderDetectMyDevices": "Detect My Devices", "HeaderDetectMyDevices": "Detect My Devices",
@ -417,6 +432,7 @@
"HeaderNewRepository": "New Repository", "HeaderNewRepository": "New Repository",
"HeaderNextEpisodePlayingInValue": "Next Episode Playing in {0}", "HeaderNextEpisodePlayingInValue": "Next Episode Playing in {0}",
"HeaderNextVideoPlayingInValue": "Next Video Playing in {0}", "HeaderNextVideoPlayingInValue": "Next Video Playing in {0}",
"HeaderNoLyrics": "No lyrics found",
"HeaderOnNow": "On Now", "HeaderOnNow": "On Now",
"HeaderOtherItems": "Other Items", "HeaderOtherItems": "Other Items",
"HeaderParentalRatings": "Parental Ratings", "HeaderParentalRatings": "Parental Ratings",
@ -523,6 +539,7 @@
"LabelAlbum": "Album", "LabelAlbum": "Album",
"LabelAlbumArtists": "Album artists", "LabelAlbumArtists": "Album artists",
"LabelAlbumGain": "Album Gain", "LabelAlbumGain": "Album Gain",
"LabelAllowContentWithTags": "Allow items with tags",
"LabelAllowedRemoteAddresses": "Remote IP address filter", "LabelAllowedRemoteAddresses": "Remote IP address filter",
"LabelAllowedRemoteAddressesMode": "Remote IP address filter mode", "LabelAllowedRemoteAddressesMode": "Remote IP address filter mode",
"LabelAllowHWTranscoding": "Allow hardware transcoding", "LabelAllowHWTranscoding": "Allow hardware transcoding",
@ -901,8 +918,6 @@
"LabelTypeText": "Text", "LabelTypeText": "Text",
"LabelUnstable": "Unstable", "LabelUnstable": "Unstable",
"LabelUser": "User", "LabelUser": "User",
"LabelUseReplayGainTagsHelp": "Scan audio files for replaygain tags and use them instead of computing LUFS value. (Uses less computing power. Will override 'LUFS Scan' option)",
"LabelUseReplayGainTags": "Use ReplayGain Tags",
"LabelUserAgent": "User agent", "LabelUserAgent": "User agent",
"LabelUserLoginAttemptsBeforeLockout": "Failed login tries before user is locked out", "LabelUserLoginAttemptsBeforeLockout": "Failed login tries before user is locked out",
"LabelUserMaxActiveSessions": "Maximum number of simultaneous user sessions", "LabelUserMaxActiveSessions": "Maximum number of simultaneous user sessions",
@ -930,6 +945,8 @@
"LearnHowYouCanContribute": "Learn how you can contribute.", "LearnHowYouCanContribute": "Learn how you can contribute.",
"LeaveBlankToNotSetAPassword": "You can leave this field blank to set no password.", "LeaveBlankToNotSetAPassword": "You can leave this field blank to set no password.",
"LibraryAccessHelp": "Select the libraries to share with this user. Administrators will be able to edit all folders using the metadata manager.", "LibraryAccessHelp": "Select the libraries to share with this user. Administrators will be able to edit all folders using the metadata manager.",
"LimitSupportedVideoResolution": "Limit maximum supported video resolution",
"LimitSupportedVideoResolutionHelp": "Use 'Maximum Allowed Video Transcoding Resolution' as maximum supported video resolution.",
"List": "List", "List": "List",
"ListView": "List View", "ListView": "List View",
"ListPaging": "{0}-{1} of {2}", "ListPaging": "{0}-{1} of {2}",
@ -946,7 +963,9 @@
"LogLevel.None": "None", "LogLevel.None": "None",
"Logo": "Logo", "Logo": "Logo",
"LogoScreensaver": "Logo Screensaver", "LogoScreensaver": "Logo Screensaver",
"Lyric": "Lyric",
"Lyricist": "Lyricist", "Lyricist": "Lyricist",
"Lyrics": "Lyrics",
"ManageLibrary": "Manage library", "ManageLibrary": "Manage library",
"ManageRecording": "Manage recording", "ManageRecording": "Manage recording",
"MapChannels": "Map Channels", "MapChannels": "Map Channels",
@ -1222,6 +1241,16 @@
"Play": "Play", "Play": "Play",
"PlayAllFromHere": "Play all from here", "PlayAllFromHere": "Play all from here",
"PlaybackData": "Playback Info", "PlaybackData": "Playback Info",
"PlaybackError.ASS_RENDER_ERROR": "An error was encountered in the ASS/SSA subtitle renderer.",
"PlaybackError.FATAL_HLS_ERROR": "A fatal error was encountered in the HLS stream.",
"PlaybackError.MEDIA_DECODE_ERROR": "Playback failed due to an error decoding the media.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Playback failed because the media is not supported by this client.",
"PlaybackError.NETWORK_ERROR": "Playback failed due to a network error.",
"PlaybackError.NO_MEDIA_ERROR": "Unable to find a valid media source to play.",
"PlaybackError.PLAYER_ERROR": "Playback failed due to a fatal player error.",
"PlaybackError.SERVER_ERROR": "Playback failed due to a server error.",
"PlaybackError.NotAllowed": "Playback of this media is not allowed.",
"PlaybackError.RateLimitExceeded": "This media cannot be played at this time due to rate limits.",
"PlaybackErrorNoCompatibleStream": "This client isn't compatible with the media and the server isn't sending a compatible media format.", "PlaybackErrorNoCompatibleStream": "This client isn't compatible with the media and the server isn't sending a compatible media format.",
"PlaybackErrorPlaceHolder": "This is a placeholder for physical media that Jellyfin cannot play. Please insert the disc to play.", "PlaybackErrorPlaceHolder": "This is a placeholder for physical media that Jellyfin cannot play. Please insert the disc to play.",
"PlaybackRate": "Playback Speed", "PlaybackRate": "Playback Speed",
@ -1497,6 +1526,7 @@
"VideoAudio": "Video Audio", "VideoAudio": "Video Audio",
"ViewAlbum": "View album", "ViewAlbum": "View album",
"ViewAlbumArtist": "View album artist", "ViewAlbumArtist": "View album artist",
"ViewLyrics": "View lyrics",
"ViewPlaybackInfo": "View playback info", "ViewPlaybackInfo": "View playback info",
"Watched": "Watched", "Watched": "Watched",
"Wednesday": "Wednesday", "Wednesday": "Wednesday",
@ -1552,7 +1582,7 @@
"EnableVppTonemapping": "Enable VPP Tone mapping", "EnableVppTonemapping": "Enable VPP Tone mapping",
"AllowVppTonemappingHelp": "Full Intel driver based tone-mapping. Currently works only on certain hardware with HDR10 videos. This has a higher priority compared to another OpenCL implementation.", "AllowVppTonemappingHelp": "Full Intel driver based tone-mapping. Currently works only on certain hardware with HDR10 videos. This has a higher priority compared to another OpenCL implementation.",
"EnableVideoToolboxTonemapping": "Enable VideoToolbox Tone mapping", "EnableVideoToolboxTonemapping": "Enable VideoToolbox Tone mapping",
"AllowVideoToolboxTonemappingHelp": "Hardware-accelerated tone-mapping provided by VideoToolbox. It works with most HDR formats, including HDR10, HDR10+, and HLG, but does not work with Dolby Vision Profile 5. This has a higher priority compared to another OpenCL implementation.", "AllowVideoToolboxTonemappingHelp": "Hardware-accelerated tone-mapping provided by VideoToolbox. It works with most HDR formats, including HDR10, HDR10+, and HLG, but does not work with Dolby Vision Profile 5. This has a higher priority compared to another Metal implementation.",
"Controls": "Controls", "Controls": "Controls",
"LabelEnableGamepad": "Enable Gamepad", "LabelEnableGamepad": "Enable Gamepad",
"EnableGamepadHelp": "Listen for input from any connected controllers. (Requires: 'TV' Display Mode)", "EnableGamepadHelp": "Listen for input from any connected controllers. (Requires: 'TV' Display Mode)",
@ -1580,6 +1610,8 @@
"EnableIntelLowPowerHevcHwEncoder": "Enable Intel Low-Power HEVC hardware encoder", "EnableIntelLowPowerHevcHwEncoder": "Enable Intel Low-Power HEVC hardware encoder",
"IntelLowPowerEncHelp": "Low-Power Encoding can keep unnecessary CPU-GPU sync. On Linux they must be disabled if the i915 HuC firmware is not configured.", "IntelLowPowerEncHelp": "Low-Power Encoding can keep unnecessary CPU-GPU sync. On Linux they must be disabled if the i915 HuC firmware is not configured.",
"LabelHardwareEncodingOptions": "Hardware encoding options", "LabelHardwareEncodingOptions": "Hardware encoding options",
"LabelEncodingFormatOptions": "Encoding format options",
"EncodingFormatHelp": "Select the video encoding that Jellyfin should transcode to. Jellyfin will use software encoding when hardware acceleration for the selected format is not available. H264 encoding will always be enabled.",
"AudioIsExternal": "The audio stream is external", "AudioIsExternal": "The audio stream is external",
"VideoBitrateNotSupported": "The video's bitrate is not supported", "VideoBitrateNotSupported": "The video's bitrate is not supported",
"UnknownVideoStreamInfo": "The video stream info is unknown", "UnknownVideoStreamInfo": "The video stream info is unknown",
@ -1598,7 +1630,7 @@
"Select": "Select", "Select": "Select",
"ThemeSong": "Theme Song", "ThemeSong": "Theme Song",
"ThemeVideo": "Theme Video", "ThemeVideo": "Theme Video",
"EnableEnhancedNvdecDecoderHelp": "Experimental NVDEC implementation, do not enable this option unless you encounter decoding errors.", "EnableEnhancedNvdecDecoderHelp": "Enhanced NVDEC implementation, disable this option to use CUVID if you encounter decoding errors.",
"EnableSplashScreen": "Enable the splash screen", "EnableSplashScreen": "Enable the splash screen",
"LabelVppTonemappingBrightness": "VPP Tone mapping brightness gain", "LabelVppTonemappingBrightness": "VPP Tone mapping brightness gain",
"LabelVppTonemappingBrightnessHelp": "Apply brightness gain in VPP tone mapping. The recommended and default values are 16 and 0.", "LabelVppTonemappingBrightnessHelp": "Apply brightness gain in VPP tone mapping. The recommended and default values are 16 and 0.",
@ -1626,10 +1658,10 @@
"ForeignPartsOnly": "Forced/Foreign parts only", "ForeignPartsOnly": "Forced/Foreign parts only",
"HearingImpairedShort": "HI/SDH", "HearingImpairedShort": "HI/SDH",
"LabelIsHearingImpaired": "For hearing impaired (SDH)", "LabelIsHearingImpaired": "For hearing impaired (SDH)",
"AllowMjpegEncoding": "Allow encoding in MJPEG format (used during trickplay generation)",
"Trickplay": "Trickplay", "Trickplay": "Trickplay",
"LabelTrickplayAccel": "Enable hardware acceleration", "LabelTrickplayAccel": "Enable hardware decoding",
"LabelTrickplayAccelHelp": "Make sure to enable 'Allow MJPEG Encoding' in Transcoding if your hardware supports it.", "LabelTrickplayAccelEncoding": "Enable hardware accelerated MJPEG encoding",
"LabelTrickplayAccelEncodingHelp": "Currently only available on QSV and VAAPI, this option has no effect on other hardware acceleration methods.",
"NonBlockingScan": "Non Blocking - queues generation, then returns", "NonBlockingScan": "Non Blocking - queues generation, then returns",
"BlockingScan": "Blocking - queues generation, blocks scan until complete", "BlockingScan": "Blocking - queues generation, blocks scan until complete",
"LabelScanBehavior": "Scan Behavior", "LabelScanBehavior": "Scan Behavior",

View file

@ -1732,5 +1732,6 @@
"EnableAudioNormalization": "Normalización de audio", "EnableAudioNormalization": "Normalización de audio",
"LabelEnableLUFSScan": "Habilitar escaneo LUFS", "LabelEnableLUFSScan": "Habilitar escaneo LUFS",
"GetThePlugin": "Obtener el complemento", "GetThePlugin": "Obtener el complemento",
"AllowSegmentDeletion": "Eliminar segmentos" "AllowSegmentDeletion": "Eliminar segmentos",
"AirPlay": "AirPlay"
} }

View file

@ -586,7 +586,7 @@
"LabelReleaseDate": "Fecha de lanzamiento", "LabelReleaseDate": "Fecha de lanzamiento",
"LabelRemoteClientBitrateLimit": "Límite de la transmisión de tasa de bits por internet (Mbps)", "LabelRemoteClientBitrateLimit": "Límite de la transmisión de tasa de bits por internet (Mbps)",
"LabelRemoteClientBitrateLimitHelp": "Especifica el bitrate máximo para los dispositivos que se encuentren fuera de la red local. Esto es útil para permitir la reproducción del contenido que tengas con una tasa de bits muy alta cuando la conexión a internet de tu servidor o la del cliente no sea lo suficientemente rápida. Esto ocasionará mayor carga, ya que el contenido que supere esta tasa de bits se convertirá para que esté dentro del límite establecido.", "LabelRemoteClientBitrateLimitHelp": "Especifica el bitrate máximo para los dispositivos que se encuentren fuera de la red local. Esto es útil para permitir la reproducción del contenido que tengas con una tasa de bits muy alta cuando la conexión a internet de tu servidor o la del cliente no sea lo suficientemente rápida. Esto ocasionará mayor carga, ya que el contenido que supere esta tasa de bits se convertirá para que esté dentro del límite establecido.",
"LabelRuntimeMinutes": "Tiempo de ejecución", "LabelRuntimeMinutes": "Duración",
"LabelSaveLocalMetadata": "Guardar imágenes en las carpetas de medios", "LabelSaveLocalMetadata": "Guardar imágenes en las carpetas de medios",
"LabelSaveLocalMetadataHelp": "Guardar imágenes directamente en las carpetas en las que estén los elementos hará que se puedan editar más fácilmente.", "LabelSaveLocalMetadataHelp": "Guardar imágenes directamente en las carpetas en las que estén los elementos hará que se puedan editar más fácilmente.",
"LabelScheduledTaskLastRan": "Última ejecución {0}, tardando {1}.", "LabelScheduledTaskLastRan": "Última ejecución {0}, tardando {1}.",
@ -1049,7 +1049,7 @@
"Yes": "Sí", "Yes": "Sí",
"Yesterday": "Ayer", "Yesterday": "Ayer",
"Absolute": "Absoluto", "Absolute": "Absoluto",
"Actor": "Interprete", "Actor": "Intérprete",
"AddToPlayQueue": "Añadir a la cola de reproducción", "AddToPlayQueue": "Añadir a la cola de reproducción",
"AirDate": "Fecha de emisión", "AirDate": "Fecha de emisión",
"Aired": "Emitido", "Aired": "Emitido",
@ -1414,7 +1414,7 @@
"PosterCard": "Tarjeta de presentación", "PosterCard": "Tarjeta de presentación",
"EnableAutoCast": "Marcar como predeterminado", "EnableAutoCast": "Marcar como predeterminado",
"LabelTonemappingDesatHelp": "Aplicar desaturación a realces que excedan este nivel de brillo. Mientras más alto el parámetro, se preservará más información de color. Este ajuste ayuda a prevenir la aparición de colores innecesariamente apagados en realces altos, cambiándolos gradualmente a blanco. Esto hace que las imágenes se vean más naturales, a cambio de reducir información relativa a los colores que estén fuera del rango. Se recomiendan los valores predeterminados 0 y 0.5.", "LabelTonemappingDesatHelp": "Aplicar desaturación a realces que excedan este nivel de brillo. Mientras más alto el parámetro, se preservará más información de color. Este ajuste ayuda a prevenir la aparición de colores innecesariamente apagados en realces altos, cambiándolos gradualmente a blanco. Esto hace que las imágenes se vean más naturales, a cambio de reducir información relativa a los colores que estén fuera del rango. Se recomiendan los valores predeterminados 0 y 0.5.",
"AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video HDR a SDR mientras se mantienen los detalles y colores en la imagen, por lo tanto es importante para representar la escena original. Actualmente solo funciona con vídeos 10bit HDR10, HLG o DoVi. Esto requiere la librería OpenCL o Cuda, según corresponda.", "AllowTonemappingHelp": "El mapeo de tonos puede transformar el rango dinámico de un video HDR a SDR mientras se mantienen los detalles y colores en la imagen, por lo tanto es importante para representar la escena original. Actualmente solo funciona con vídeos 10bit HDR10, HLG o DoVi. Esto requiere la librería GPGPU.",
"LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se usará para el mapeo de tonos. La parte izquierda del punto es el número de plataforma, la derecha es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere especificar el archivo ejecutable FFmpeg con el método de aceleración por Hardware para OpenCL.", "LabelOpenclDeviceHelp": "Este es el dispositivo OpenCL que se usará para el mapeo de tonos. La parte izquierda del punto es el número de plataforma, la derecha es el número de dispositivo en la plataforma. El valor predeterminado es 0.0. Se requiere especificar el archivo ejecutable FFmpeg con el método de aceleración por Hardware para OpenCL.",
"LabelMaxMuxingQueueSizeHelp": "El número máximo de paquetes que se pueden almacenar en buffer mientras se espera a que se inicialicen todos los flujos. Intenta aumentar este valor si aún encuentras el mensaje de error \"Demasiados paquetes en buffer para transmitir\" en los registros de FFmpeg. El valor recomendado es 2048.", "LabelMaxMuxingQueueSizeHelp": "El número máximo de paquetes que se pueden almacenar en buffer mientras se espera a que se inicialicen todos los flujos. Intenta aumentar este valor si aún encuentras el mensaje de error \"Demasiados paquetes en buffer para transmitir\" en los registros de FFmpeg. El valor recomendado es 2048.",
"LabelTonemappingPeakHelp": "Se omitirán los picos de referencia con este valor. Util cuando la información de pico incrustada en los metadatos de imagen no es confiable o cuando se hace un mapeo de tonos de un rango bajo a uno más alto. Se recomiendan los valores predeterminados 100 y 0.", "LabelTonemappingPeakHelp": "Se omitirán los picos de referencia con este valor. Util cuando la información de pico incrustada en los metadatos de imagen no es confiable o cuando se hace un mapeo de tonos de un rango bajo a uno más alto. Se recomiendan los valores predeterminados 100 y 0.",
@ -1629,7 +1629,7 @@
"EnableRewatchingNextUpHelp": "Mostrar episodios vistos en la sección 'Volver a ver'.", "EnableRewatchingNextUpHelp": "Mostrar episodios vistos en la sección 'Volver a ver'.",
"EnableRewatchingNextUp": "Activar Volver a ver", "EnableRewatchingNextUp": "Activar Volver a ver",
"Digital": "Digital", "Digital": "Digital",
"EnableEnhancedNvdecDecoderHelp": "Implementación experimental de NVDEC, no habilite esta opción a menos que encuentre errores de decodificación.", "EnableEnhancedNvdecDecoderHelp": "Implementación mejorada de NVDEC, deshabilite esta opción para usar CUVID si encuentra errores de decodificación.",
"ThemeSong": "Tema principal", "ThemeSong": "Tema principal",
"Sample": "Muestra", "Sample": "Muestra",
"Scene": "Escena", "Scene": "Escena",
@ -1798,5 +1798,37 @@
"AllowVideoToolboxTonemappingHelp": "Aceleración por hardware en mapeo de tonos por VideoToolbox. Funciona con la mayoría de los formatos HDR, incluidos HDR10, HDR10+ y HLG, pero no funciona con Dolby Vision Profile 5. Esto tiene una prioridad más alta en comparación con otra implementación de OpenCL.", "AllowVideoToolboxTonemappingHelp": "Aceleración por hardware en mapeo de tonos por VideoToolbox. Funciona con la mayoría de los formatos HDR, incluidos HDR10, HDR10+ y HLG, pero no funciona con Dolby Vision Profile 5. Esto tiene una prioridad más alta en comparación con otra implementación de OpenCL.",
"LabelWebVersion": "Versión web", "LabelWebVersion": "Versión web",
"DlnaMovedMessage": "La funcionalidad DLNA se ha movido a complementos.", "DlnaMovedMessage": "La funcionalidad DLNA se ha movido a complementos.",
"LabelServerVersion": "Versión del servidor" "LabelServerVersion": "Versión del servidor",
"AllowMjpegEncoding": "Permitir codificación en formato MJPEG (utilizado durante la generación de trickplay)",
"Trickplay": "Trickplay",
"LabelTrickplayAccel": "Habilitar aceleración por hardware",
"LabelScanBehavior": "Comportamiento de Escaneo",
"ConfirmDeleteSeries": "Eliminar esta serie eliminará TODOS {0} episodios tanto del sistema de archivos como de tu biblioteca de medios. ¿Estás seguro de que deseas continuar?",
"DeleteEntireSeries": "Eliminar {0} Episodios",
"DeleteSeries": "Eliminar Series",
"DeleteEpisode": "Eliminar Episodio",
"HeaderDeleteSeries": "Eliminar Series",
"LabelTrickplayAccelHelp": "Asegúrate de habilitar Permitir codificación MJPEG en Transcodificación si tu hardware lo soporta.",
"AllowContentWithTagsHelp": "Mostrar sólo contenidos con al menos una de las etiquetas especificadas.",
"BlockContentWithTagsHelp": "Ocultar contenidos con al menos una de las etiquetas especificadas.",
"PlaybackError.ASS_RENDER_ERROR": "Se ha producido un error en el renderizador de subtítulos ASS/SSA.",
"PlaybackError.MEDIA_DECODE_ERROR": "Reproducción fallida debido a un error en la decodificación del contenido.",
"EnableSmoothScroll": "Activar desplazamiento suave",
"Lyric": "Letra",
"LimitSupportedVideoResolution": "Limitar resolución de video máxima soportada",
"LimitSupportedVideoResolutionHelp": "Utilice \"Resolución de transcodificación de vídeo máxima permitida\" como resolución de vídeo máxima admitida.",
"LabelEncodingFormatOptions": "Opciones de formato de codificación",
"AirPlay": "AirPlay",
"PlaybackError.FATAL_HLS_ERROR": "Un error fatal se encontró en el stream HLS.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "La reproducción falló porque el medio no es compatible con este cliente.",
"PlaybackError.NETWORK_ERROR": "La reproducción falló por un error de red.",
"PlaybackError.NO_MEDIA_ERROR": "No ha sido posible encontrar un medio válido para reproducir.",
"PlaybackError.PLAYER_ERROR": "La reproducción falló por un error fatal del reproductor.",
"PlaybackError.SERVER_ERROR": "La reproducción falló por un error del servidor.",
"PlaybackError.NotAllowed": "La reproducción de este medio no está permitida.",
"PlaybackError.RateLimitExceeded": "Este medio no puede reproducirse en este momento debído a límites.",
"EnableLibrary": "Activar la librería",
"EnableLibraryHelp": "Desactivando la biblioteca hará que no sea visible para ningún usuario.",
"ConfirmDeleteLyrics": "Borrando estas letras también las borrará tanto del sistema de ficheros como de tu librería de medios. ¿Estás seguro de que deseas continuar?",
"DeleteLyrics": "Borrar letras"
} }

View file

@ -1792,5 +1792,69 @@
"ChannelResolutionSDPAL": "SD (PAL)", "ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionFullHD": "Täysi HD", "ChannelResolutionFullHD": "Täysi HD",
"ChannelResolutionUHD4K": "UHD (4K)", "ChannelResolutionUHD4K": "UHD (4K)",
"AllowSubtitleManagement": "Anna tämän käyttäjän muuttaa tekstityksiä" "AllowSubtitleManagement": "Anna tämän käyttäjän muuttaa tekstityksiä",
"PlaybackError.FATAL_HLS_ERROR": "HLS-virrassa ilmeni peruuttamaton virhe.",
"HeaderDeleteSeries": "Poista sarja",
"LabelTrickplayThreadsHelp": "Säikeiden lukumäärä, joka syötetään ffmpeg:n '-threads' argumenttiin.",
"ConfirmDeleteSeries": "Tämän sarjan poisto tulee poistamaan KAIKKI {0} jaksoa tiedostojärjestelmältä ja mediakirjastostasi. Haluatko varmasti jatkaa?",
"DeleteEntireSeries": "Poista {0} jaksoa",
"DeleteSeries": "Poista sarja",
"DeleteEpisode": "Poista jakso",
"DeleteName": "Poista {0}",
"PlaybackError.ASS_RENDER_ERROR": "ASS/SSA tekstityksien esittäjässä ilmeni virhe.",
"EnableSmoothScroll": "Käytä sujuvaa vieritystä",
"Lyric": "Sanoitus",
"LimitSupportedVideoResolution": "Rajoita videon tuettua enimmäistarkkuutta",
"EncodingFormatHelp": "Valitse enkoodaus, johon Jellyfinin tulisi transkoodata. Jellyfin käyttää softaenkoodausta kun laitteistokiihdytys ei ole saatavilla valitulle formaatille. H264 enkoodaus on aina käytettävissä.",
"LabelProcessPriorityHelp": "Tämän asetuksen alentaminen tai nostaminen määrittää, miten suoritin priorisoi ffmpeg-trickplay-generointiprosessia suhteessa muihin prosesseihin. Jos huomaat hidastumista trickplay-kuvien luonnin aikana, mutta et halua lopettaa niiden luontia kokonaan, kokeile tämän sekä säikeiden määrän alentamista .",
"LabelEncodingFormatOptions": "Enkoodauksen muotoasetukset",
"Trickplay": "Trickplay",
"LabelScanBehavior": "Skannauskäyttäytyminen",
"PriorityHigh": "Korkea",
"PriorityAboveNormal": "Suurempi kuin normaali",
"PriorityNormal": "Normaali",
"PriorityBelowNormal": "Pienempi kuin normaali",
"EnableLibrary": "Ota kirjasto käyttöön",
"EnableLibraryHelp": "Kirjaston poistaminen käytöstä piilottaa sen kaikilta käyttäjiltä.",
"LabelAllowContentWithTags": "Salli tagatut kohteet",
"LimitSupportedVideoResolutionHelp": "Käytä 'Suurin sallittu videon transkoodaustarkkuus' suurimpana tuettuna videoresoluutiona.",
"PlaybackError.MEDIA_DECODE_ERROR": "Toisto epäonnistui: Median dekoodausvirhe",
"PlaybackError.MEDIA_NOT_SUPPORTED": "Toisto epäonnistui: Pääte ei tue mediaa.",
"PlaybackError.NETWORK_ERROR": "Toisto epäonnistui: Verkkovirhe",
"PlaybackError.NO_MEDIA_ERROR": "Kelvollista medialähdettä ei löytynyt.",
"PlaybackError.PLAYER_ERROR": "Toisto epäonnistui: kriittinen toistovirhe.",
"PlaybackError.SERVER_ERROR": "Toisto epäonnistui: Palvelinvirhe",
"PlaybackError.NotAllowed": "Tämän median toisto ei ole sallittua.",
"EnableVideoToolboxTonemapping": "Ota käyttöön VideoToolbox Tone mapping",
"AllowMjpegEncoding": "Salli enkoodaus MJPEG formaattiin (käytetään trickplay luomisessa)",
"LabelTrickplayAccel": "Ota käyttöön laitteistokiihdytys",
"LabelTrickplayAccelHelp": "Varmista, että otat käyttöön \"Salli MPEG enkoodaus' Transkoodausasetuksissa jos laitteistosi tukee sitä.",
"LabelScanBehaviorHelp": "Oletuskäyttäytyminen on ei-blokkaava, joka lisää median kirjastoon ennen trickplay luontia. Blokkaus varmistaa, että trickplay-tiedostot luodaan ennen kuin media lisätään kirjastoon, mutta tekee skannauksista merkittävästi pitempiä.",
"PriorityIdle": "Toimeton",
"LabelProcessPriority": "Prosessiprioriteetti",
"LabelImageInterval": "Kuvien aikaväli",
"AirPlay": "AirPlay",
"AllowContentWithTagsHelp": "Näytä vain media, joilla on vähintään yksi määritellyistä tageista.",
"BlockContentWithTagsHelp": "Piilota media, joilla on ainakin yksi määritellyistä tageista.",
"LabelUseReplayGainTagsHelp": "Skannaa äänitiedostot replaygain tageista ja käytä niitä LUFS-arvon laskemisen sijaan. (Käyttää vähemmän laskentatehoa. Ylikirjoittaa 'LUFS skannaus' asetuksen)",
"LabelUseReplayGainTags": "Käytä ReplayGain-tageja",
"PlaybackError.RateLimitExceeded": "Tätä mediaa ei voi juuri nyt toistaa toistomäärärajoitusten vuoksi.",
"LabelWidthResolutionsHelp": "Pilkulla eroteltu lista leveyksistä (px), joille Trickplay-kuvat generoidaan. Kaikkien kuvien tulisi generoitua suhteessa lähdeaineistoon, joten 16:9 -videolla leveys 320px muuttuu noin 320x180 -kokoiseksi.",
"LabelExtractTrickplayDuringLibraryScanHelp": "Generoi trickplay-kuvat, kun videot tuodaan kirjaston tarkistuksen aikana. Muussa tapauksessa ne luodaan trickplay-kuvien ajoitetun tehtävän aikana. Jos generaatio on asetettu ei-blokkaavaksi, tällä ei ole vaikutusta siihen, kuinka kauan kirjaston tarkistuksessa kestää.",
"LabelImageIntervalHelp": "Jokaisen uuden trickplay-kuvan aikaväli (ms).",
"LabelTileWidthHelp": "Maksimäärä kuvia laattaa kohden X-akselilla.",
"LabelTileWidth": "Laattaleveys",
"LabelWidthResolutions": "Leveysresoluutiot",
"LabelTileHeight": "Laattakorkeus",
"LabelJpegQuality": "JPEG laatu",
"LabelJpegQualityHelp": "JPEG kompression laatu trickplay-kuville.",
"LabelQscale": "Qscale",
"LabelTileHeightHelp": "Maksimäärä kuvia laattaa kohden Y-akselilla.",
"LabelTrickplayThreads": "FFmpeg säikeet",
"OptionExtractTrickplayImage": "Ota käyttöön trickplay-kuvien luonti",
"LabelQscaleHelp": "Kuvien laatuasteikko, jonka ffmpeg tuottaa, jossa 2 on korkein laatu ja 31 alin.",
"ExtractTrickplayImagesHelp": "Trickplay-kuvat ovat samankaltaisia kuin kappalekuvat, paitsi että ne ovat koko sisällön pituudelta ja niitä käytetään esikatseluna videota selattaessa.",
"NonBlockingScan": "Ei-blokkaava - asettaa generoinnin jonoon ja palaa",
"BlockingScan": "Blokkaava - asettaa generoinnin jonoon ja estää skannauksen ennen kuin se on valmis",
"LabelExtractTrickplayDuringLibraryScan": "Luo trickplay-kuvat kirjaston skannauksen yhteydessä"
} }

View file

@ -370,7 +370,7 @@
"Descending": "Décroissant", "Descending": "Décroissant",
"Depressed": "Diminuer", "Depressed": "Diminuer",
"DeleteDevicesConfirmation": "Voulez-vous vraiment supprimer tous les appareils ? Toutes les autres sessions seront déconnectées. Les appareils réapparaîtront la prochaine fois qu'un utilisateur se connectera.", "DeleteDevicesConfirmation": "Voulez-vous vraiment supprimer tous les appareils ? Toutes les autres sessions seront déconnectées. Les appareils réapparaîtront la prochaine fois qu'un utilisateur se connectera.",
"AllowTonemappingHelp": "Le tone-mapping peut transformer la plage dynamique dune vidéo de HDR à SDR tout en maintenant les détails et les couleurs de limage, qui sont des informations très importantes pour représenter la scène originale. Cette fonction prend en charge uniquement les vidéos HDR10, HLG ou DoVi et nécessite lenvironnement dexécution OpenCL ou CUDA correspondant.", "AllowTonemappingHelp": "Le tone-mapping peut transformer la plage dynamique dune vidéo de HDR à SDR tout en maintenant les détails et les couleurs de limage, qui sont des informations très importantes pour représenter la scène originale. Cette fonction prend en charge uniquement les vidéos HDR10, HLG ou DoVi et nécessite un environnement dexécution GPGPU correspondant.",
"LabelEasyPinCode": "NIP facile", "LabelEasyPinCode": "NIP facile",
"LabelDynamicExternalId": "ID {0}", "LabelDynamicExternalId": "ID {0}",
"LabelDownloadLanguages": "Téléchargement des langues", "LabelDownloadLanguages": "Téléchargement des langues",
@ -1334,5 +1334,14 @@
"MediaInfoAnamorphic": "Anamorphique", "MediaInfoAnamorphic": "Anamorphique",
"MediaInfoLayout": "Agencement", "MediaInfoLayout": "Agencement",
"MenuOpen": "Ouvrir le menu", "MenuOpen": "Ouvrir le menu",
"MenuClose": "Fermer le menu" "MenuClose": "Fermer le menu",
"AllowContentWithTagsHelp": "Afficher uniquement les médias avec un ou plusieurs des étiquettes spécifiées.",
"BlockContentWithTagsHelp": "Masquer les médias avec un ou plusieurs des étiquettes spécifiées.",
"ConfirmDeleteSeries": "La suppression de cette série entraînera la suppression des {0} épisodes depuis le disque ET la médiathèque. Êtes-vous sûr de vouloir continuer?",
"DeleteEpisode": "Supprimer lépisode",
"EnableLibrary": "Activer la médiathèque",
"EnableLibraryHelp": "La désactivation de la médiathèque la masquera depuis toutes les vues utilisateur.",
"AirPlay": "AirPlay",
"DeleteEntireSeries": "Supprimer {0} épisodes",
"DeleteSeries": "Supprimer la série"
} }

View file

@ -211,7 +211,7 @@
"GroupVersions": "Grouper les versions", "GroupVersions": "Grouper les versions",
"GuideProviderLogin": "Connexion", "GuideProviderLogin": "Connexion",
"GuideProviderSelectListings": "Sélectionner les programmes", "GuideProviderSelectListings": "Sélectionner les programmes",
"H264CrfHelp": "Le facteur de débit constant (CRF) est le paramètre de qualité par défaut pour les encodeurs x264 et x265. Choisir une valeur comprise entre 0 et 51 en tenant compte qu'une valeur inférieure produit une vidéo de meilleure qualité au prix d'une taille de fichier plus importante. Les valeurs considérées comme saines sont comprises entre 18 et 28. Les valeurs par défaut, 23 pour x264 et 28 pour x265, peuvent être utilisées comme point de départ pour le réglage.", "H264CrfHelp": "Le facteur de débit constant (CRF) est le paramètre de qualité par défaut pour les encodeurs x264 et x265. Choisir une valeur comprise entre 0 et 51 en tenant compte qu'une valeur inférieure produit une vidéo de meilleure qualité au prix d'une taille de fichier plus importante. Les valeurs considérées comme saines sont comprises entre 18 et 28. Les valeurs par défaut, 23 pour x264 et 28 pour x265, peuvent être utilisées comme point de départ pour le réglage. Les encodages matériels n'utilisent pas ces paramètres.",
"EncoderPresetHelp": "Choisir un profil d'encodage plus rapide améliorera la performance tandis qu'un profil plus lent favorisera la qualité.", "EncoderPresetHelp": "Choisir un profil d'encodage plus rapide améliorera la performance tandis qu'un profil plus lent favorisera la qualité.",
"HDPrograms": "Programmes HD", "HDPrograms": "Programmes HD",
"HardwareAccelerationWarning": "L'activation de l'accélération matérielle peut provoquer l'instabilité de certains environnements. Merci de vous assurer que le système d'exploitation et les pilotes vidéo sont tous complètement à jour. Si vous avez des difficultés pour lire des vidéos après l'activation, merci de remettre ce paramètre sur 'Aucun'.", "HardwareAccelerationWarning": "L'activation de l'accélération matérielle peut provoquer l'instabilité de certains environnements. Merci de vous assurer que le système d'exploitation et les pilotes vidéo sont tous complètement à jour. Si vous avez des difficultés pour lire des vidéos après l'activation, merci de remettre ce paramètre sur 'Aucun'.",
@ -1384,7 +1384,7 @@
"LabelTonemappingRange": "Gamme de mappage tonal", "LabelTonemappingRange": "Gamme de mappage tonal",
"TonemappingAlgorithmHelp": "Le mappage tonal peut être affiné. Si vous n'êtes pas familier avec ces réglages, il est conseillé de choisir les valeurs recommandées. L'algorithme de mappage recommandé est 'BT.2390'.", "TonemappingAlgorithmHelp": "Le mappage tonal peut être affiné. Si vous n'êtes pas familier avec ces réglages, il est conseillé de choisir les valeurs recommandées. L'algorithme de mappage recommandé est 'BT.2390'.",
"LabelTonemappingAlgorithm": "Sélectionner l'algorithme de mappage tonal à utiliser", "LabelTonemappingAlgorithm": "Sélectionner l'algorithme de mappage tonal à utiliser",
"AllowTonemappingHelp": "Le mappage tonal est capable de convertir une gamme dynamique HDR en SDR tout en maintenant les détails et les couleurs d'image si importants au rendu de la scène originale. Pour le moment, ne fonctionne qu'avec les vidéos HDR10 10bits, HLG, DoVi et requiert les environnements d'exécution OpenCL et CUDA correspondant.", "AllowTonemappingHelp": "Le mappage tonal est capable de convertir une gamme dynamique HDR en SDR tout en maintenant les détails et les couleurs d'image si importants au rendu de la scène originale. Pour le moment, ne fonctionne qu'avec les vidéos HDR10 10bits, HLG, DoVi et requiert l'environnement d'exécution GPGPU correspondant.",
"EnableTonemapping": "Activer le mappage tonal", "EnableTonemapping": "Activer le mappage tonal",
"LabelOpenclDeviceHelp": "Ce dispositif OpenCL est utilisé pour le mappage tonal. La partie à gauche du point est le numéro de plate-forme et la partie à droite est le numéro du dispositif sur la plate-forme. La valeur par défaut est 0,0. Le fichier de l'application FFmpeg contenant l'accélération matérielle OpenCL est nécessaire.", "LabelOpenclDeviceHelp": "Ce dispositif OpenCL est utilisé pour le mappage tonal. La partie à gauche du point est le numéro de plate-forme et la partie à droite est le numéro du dispositif sur la plate-forme. La valeur par défaut est 0,0. Le fichier de l'application FFmpeg contenant l'accélération matérielle OpenCL est nécessaire.",
"LabelOpenclDevice": "Dispositif OpenCL", "LabelOpenclDevice": "Dispositif OpenCL",
@ -1649,7 +1649,7 @@
"OriginalAirDate": "Date de diffusion originale", "OriginalAirDate": "Date de diffusion originale",
"MessageUnauthorizedUser": "Vous n'êtes pas autorisé à accéder au serveur pour le moment. Veuillez contacter votre administrateur de serveur pour plus d'informations.", "MessageUnauthorizedUser": "Vous n'êtes pas autorisé à accéder au serveur pour le moment. Veuillez contacter votre administrateur de serveur pour plus d'informations.",
"Digital": "Numérique", "Digital": "Numérique",
"EnableEnhancedNvdecDecoderHelp": "Implémentation expérimentale de NVDEC, à n'utiliser que si des erreurs apparaissent lors du décodage.", "EnableEnhancedNvdecDecoderHelp": "Implémentation NVDEC améliorée, désactivez cette option pour utiliser CUVID si vous rencontrez des erreurs de décodage.",
"HomeVideosPhotos": "Vidéos et photos personnelles", "HomeVideosPhotos": "Vidéos et photos personnelles",
"LabelTextWeight": "Poids de la police", "LabelTextWeight": "Poids de la police",
"Bold": "Gras", "Bold": "Gras",
@ -1798,5 +1798,74 @@
"LabelUseReplayGainTagsHelp": "Scanne les fichiers audio à la recherche de tags replaygain et les utilise au lieu de calculer la valeur LUFS. (Utilise moins de ressources. Passe outre l'option 'Scanne LUFS')", "LabelUseReplayGainTagsHelp": "Scanne les fichiers audio à la recherche de tags replaygain et les utilise au lieu de calculer la valeur LUFS. (Utilise moins de ressources. Passe outre l'option 'Scanne LUFS')",
"LabelUseReplayGainTags": "Utiliser les tags ReplayGain", "LabelUseReplayGainTags": "Utiliser les tags ReplayGain",
"EnableVideoToolboxTonemapping": "Activer le tone-mapping VideoToolbox", "EnableVideoToolboxTonemapping": "Activer le tone-mapping VideoToolbox",
"AllowVideoToolboxTonemappingHelp": "Tone-mapping via l'accélération matérielle par VideoToolbox. Fonctionne avec la majorité des formats HDR, incluant le HDR10, HDR10+, et HLG, mais ne fonctionne pas avec le Dolby Vision Profile 5. Cela a une priorité plus élevé comparé à une autre implémentation OpenCL." "AllowVideoToolboxTonemappingHelp": "Tone-mapping via l'accélération matérielle par VideoToolbox. Fonctionne avec la majorité des formats HDR, incluant le HDR10, HDR10+, et HLG, mais ne fonctionne pas avec le Dolby Vision Profile 5. Cela a une priorité plus élevé comparé à une autre implémentation Metal.",
"PlaybackError.FATAL_HLS_ERROR": "Une erreur fatale a été rencontrée dans le flux HLS.",
"PlaybackError.ASS_RENDER_ERROR": "Une erreur a été rencontrée dans le moteur de rendu des sous-titres ASS/SSA.",
"AllowContentWithTagsHelp": "Afficher uniquement les médias comportant au moins l'une des étiquettes spécifiées.",
"BlockContentWithTagsHelp": "Masquer les médias contenant au moins l'une des étiquettes spécifiées.",
"PlaybackError.MEDIA_DECODE_ERROR": "La lecture a échouée en raison d'une erreur de décodage du média.",
"LabelAllowContentWithTags": "Autoriser les objets comportants des étiquettes",
"ConfirmDeleteSeries": "La suppression de cette série effacera l'ENTIÈRETÉ des {0} épisodes à la fois de votre système de ficher et de votre médiathèque. Êtes-vous sur de vouloir continuer ?",
"DeleteEntireSeries": "Supprimer {0} Épisodes",
"DeleteSeries": "Supprimer Séries",
"DeleteEpisode": "Suppri",
"HeaderDeleteSeries": "Supprimer Séries",
"EnableSmoothScroll": "Activer le défilement fluide",
"Lyric": "Parole",
"LimitSupportedVideoResolution": "Limiter la résolution vidéo maximale prise en charge",
"LimitSupportedVideoResolutionHelp": "Utiliser la 'Résolution de transcodage vidéo maximale autorisée' comme résolution vidéo maximale prise en charge.",
"EnableLibrary": "Activer la médiathèque",
"EnableLibraryHelp": "La désactivation de la médiathèque la masquera de toutes les vues utilisateur.",
"AirPlay": "Diffusion",
"LabelEncodingFormatOptions": "Options de format d'encodage",
"AllowMjpegEncoding": "Autoriser l'encodage au format MJPEG (utilisé lors de la génération trickplay)",
"EncodingFormatHelp": "Sélectionnez l'encodage vidéo vers lequel Jellyfin doit transcoder. Jellyfin utilisera le codage logiciel lorsque l'accélération matérielle pour le format sélectionné n'est pas disponible. Lencodage H264 sera toujours activé.",
"PlaybackError.SERVER_ERROR": "La lecture a échoué à cause d'une erreur serveur.",
"PlaybackError.NotAllowed": "La lecture de ce média n'est pas autorisée.",
"PlaybackError.RateLimitExceeded": "Ce média ne peut pas être lu à cause de la limite de débit.",
"LabelImageInterval": "Intervalle d'image",
"PriorityIdle": "Inactif",
"LabelWidthResolutionsHelp": "Liste séparée par des virgules des largeurs (px) auxquelles les images trickplay seront générées. Toutes les images doivent être générées proportionnellement à la source, donc une largeur de 320 sur une vidéo 16:9 aboutit à environ 320 x 180.",
"LabelExtractTrickplayDuringLibraryScanHelp": "Générez des images trickplay lorsque les vidéos sont importées pendant l'analyse de la médiathèque. Dans le cas contraire, elles seront extraites lors de la tâche programmée trickplay images. Si la génération est définie sur non bloquante, cela naffectera pas le temps nécessaire à lanalyse de la médiathèque.",
"LabelJpegQualityHelp": "La qualité de compression JPEG pour les images trickplay.",
"LabelQscaleHelp": "L'échelle de qualité des images générées par ffmpeg, 2 étant la qualité la plus élevée et 31 la plus basse.",
"ExtractTrickplayImagesHelp": "Les images Trickplay sont similaires aux images de chapitre, sauf qu'elles couvrent toute la longueur du contenu et sont utilisées pour afficher un aperçu lors du parcours des vidéos.",
"Trickplay": "Trickplay",
"PriorityHigh": "Élevé",
"LabelProcessPriorityHelp": "Un réglage inférieur ou supérieur déterminera la manière dont le processeur donne la priorité au processus de génération de trickplay ffmpeg par rapport aux autres processus. Si vous remarquez un ralentissement lors de la génération d'images trickplay mais que vous ne souhaitez pas arrêter complètement leur génération, essayez de réduire ce ralentissement en modifiant le nombre de threads.",
"LabelImageIntervalHelp": "Intervalle de temps (ms) entre chaque nouvelle image trickplay.",
"LabelWidthResolutions": "Largeur des résolutions",
"LabelTrickplayAccel": "Activer l'accélération matérielle",
"LabelTrickplayAccelHelp": "Assurez-vous d'activer « Autoriser l'encodage MJPEG » dans Transcodage si votre matériel le prend en charge.",
"NonBlockingScan": "Non bloquant - génération de files d'attente, puis retour",
"BlockingScan": "Bloquant - génération de files d'attente, analyse des blocs jusqu'à la fin",
"LabelScanBehavior": "Comportement du scan",
"LabelScanBehaviorHelp": "Le comportement par défaut est non bloquant, ce qui ajoutera des médias à la médiathèque avant que la génération trickplay ne soit terminée. Le blocage garantira que les fichiers trickplay sont générés avant que le média ne soit ajouté à la médiathèque, mais rendra les analyses beaucoup plus longues.",
"PriorityAboveNormal": "Au dessus de la normal",
"PriorityNormal": "Normal",
"PriorityBelowNormal": "En dessous de la normal",
"LabelProcessPriority": "Priorité processus",
"LabelTileWidth": "Largeur des tuiles",
"LabelTileWidthHelp": "Nombre maximum d'images par tuile dans la direction X.",
"LabelTileHeight": "Hauteur des tuiles",
"LabelTileHeightHelp": "Nombre maximum d'images par tuile dans la direction Y.",
"LabelJpegQuality": "Qualité JPEG",
"LabelQscale": "Echelle de qualité",
"LabelTrickplayThreads": "FFmpeg Threads",
"OptionExtractTrickplayImage": "Activer l'extraction d'images trickplay",
"LabelTrickplayThreadsHelp": "Le nombre de threads à transmettre à l'argument '-threads' de ffmpeg.",
"LabelExtractTrickplayDuringLibraryScan": "Extraire les images trickplay pendant l'analyse de la médiathèque",
"PlaybackError.MEDIA_NOT_SUPPORTED": "La lecture a échoué car le média n'est pas pris en charge par ce client.",
"PlaybackError.NETWORK_ERROR": "La lecture a échoué à cause d'une erreur réseau.",
"PlaybackError.NO_MEDIA_ERROR": "Impossible de trouver une source multimédia valide à lire.",
"PlaybackError.PLAYER_ERROR": "La lecture a échoué en raison d'une erreur fatale du joueur.",
"LabelTrickplayAccelEncoding": "Activer l'encodage MJPEG accéléré par le matériel",
"LabelTrickplayAccelEncodingHelp": "Actuellement disponible uniquement sur QSV et VAAPI, cette option n'a aucun effet sur les autres méthodes d'accélération matérielle.",
"ErrorDeletingLyrics": "Une erreur est survenu lors de la suppression des paroles du serveur. S'il vous plaît verifier que Jellyfin peut modifier les fichier dans le dossier multimedia et réessayez.",
"HeaderDeleteLyrics": "Supprimez ces paroles",
"ConfirmDeleteLyrics": "En supprimant ces paroles vous les supprimez a la fois de votre systeme de fichier et de votre bibliothèque. Êtes vous sure de vouloir continuez ?",
"DeleteLyrics": "Supprimez ces paroles",
"HeaderNoLyrics": "Aucune paroles n'ont êtes trouves",
"Lyrics": "Paroles",
"ViewLyrics": "Voir les paroles"
} }

View file

@ -853,7 +853,7 @@
"EnableBlurHashHelp": "תמונות שעדיין נטענות יוצגו עם מציין מיקום ייחודי.", "EnableBlurHashHelp": "תמונות שעדיין נטענות יוצגו עם מציין מיקום ייחודי.",
"Bwdif": "BWDIF", "Bwdif": "BWDIF",
"ButtonCast": "שדר למכשיר", "ButtonCast": "שדר למכשיר",
"AllowTonemappingHelp": "מיפוי-טונים מאפשר המרה של וידאו מ-HDR ל-SDR תוך שמירה על פרטי וצבעי תמונה, החשובים לשימור מידע מהסצנה המקורית. כרגע עובד רק בקידוד קבצים של HDR10, HLG ו-DoVi. תכונה זו מצריכה הרצה של OpenCL או CUDA בהתאם.", "AllowTonemappingHelp": "מיפוי-טונים מאפשר המרה של וידאו מ-HDR ל-SDR תוך שמירה על פרטי וצבעי תמונה, החשובים לשימור מידע מהסצנה המקורית. כרגע עובד רק בקידוד קבצים של HDR10, HLG ו-DoVi. תכונה זו מצריכה הרצה של GPGPU בהתאם.",
"Subtitle": "כתובית", "Subtitle": "כתובית",
"StopRecording": "הפסק הקלטה", "StopRecording": "הפסק הקלטה",
"SortByValue": "מיין לפי {0}", "SortByValue": "מיין לפי {0}",
@ -1178,10 +1178,10 @@
"TabStreaming": "הזרמה", "TabStreaming": "הזרמה",
"Metadata": "נתוני על", "Metadata": "נתוני על",
"AllowSegmentDeletion": "מחק אותות", "AllowSegmentDeletion": "מחק אותות",
"AllowSegmentDeletionHelp": "מחק אותות ישנים אחרי שהם נשלחו ללקוח. זה ימנע שמירת כל הקובץ המפוענח על הדיסק. עובד רק כאשר מצב חנק פעיל. כבה אפשרות זו אם הינך חווה בעיות בנגן.", "AllowSegmentDeletionHelp": "מחק חלקים ישנים אחרי שהם הורדו על ידי המכשיר. זה ימנע שמירת כל הקובץ המפוענח על הדיסק. כבה אפשרות זו אם הינך חווה בעיות בנגן.",
"LabelThrottleDelaySeconds": "חנוק אחרי", "LabelThrottleDelaySeconds": "חנוק אחרי",
"LabelSegmentKeepSeconds": "זמן שמירת אותות", "LabelSegmentKeepSeconds": "זמן שמירת אותות",
"LabelSegmentKeepSecondsHelp": "זמן בשניות שבהן הקטעים צריכים להישאר לפני שהם נדרסים. צריך להיות גדול מ\"חנוק אחרי\". עובד רק כאשר מחיקת אות מאופשרת.", "LabelSegmentKeepSecondsHelp": "זמן בשניות שבהן הקטעים צריכים להישאר אחרי שהם הורדו על ידי המכשיר. עובד רק כאשר מחיקת אות מאופשרת.",
"GoHome": "מעבר למסך הבית", "GoHome": "מעבר למסך הבית",
"LabelEnableDlnaClientDiscoveryIntervalHelp": "קובע את משך הזמן בשניות בין שני חיפושי SSDP.", "LabelEnableDlnaClientDiscoveryIntervalHelp": "קובע את משך הזמן בשניות בין שני חיפושי SSDP.",
"LabelDropSubtitleHere": "גרור קובץ כתוביות לכאן, או לחץ לבחירת קובץ.", "LabelDropSubtitleHere": "גרור קובץ כתוביות לכאן, או לחץ לבחירת קובץ.",
@ -1258,5 +1258,28 @@
"LabelKodiMetadataSaveImagePathsHelp": "ההגדרה מומלצת אם יש לך שמות תמונות לא לפי הקווים המנחים של קודי.", "LabelKodiMetadataSaveImagePathsHelp": "ההגדרה מומלצת אם יש לך שמות תמונות לא לפי הקווים המנחים של קודי.",
"LabelKodiMetadataUser": "שמור מידע צפייה של המשתמש בקובץ NFO עבור", "LabelKodiMetadataUser": "שמור מידע צפייה של המשתמש בקובץ NFO עבור",
"LabelKodiMetadataUserHelp": "שמור מידע צפייה בקובץ NFO לשימוש של אפליקציות אחרות.", "LabelKodiMetadataUserHelp": "שמור מידע צפייה בקובץ NFO לשימוש של אפליקציות אחרות.",
"LabelMaxDaysForNextUp": "מקסימום ימים עבור 'הבא בתור'" "LabelMaxDaysForNextUp": "מקסימום ימים עבור 'הבא בתור'",
"BlockContentWithTagsHelp": "מסתיר תוכן עם לפחות אחת מהתגיות הנתונות.",
"ConfirmDeleteSeries": "מחיקת הסדרה הזאת ימחק את כול {0} הפרקים מהמערכת ומתקיית התכנים. האם אתה בתוך שתרצה להמשיך?",
"EnableLibrary": "להפעיל את התקייה",
"EnableLibraryHelp": "מחיקת התקייה יסתיר אותה מכול צפיות המשתמש.",
"AllowContentWithTagsHelp": "רק מראה תכנים שיש בהם לפחות אחת מהתגיות המצורפות.",
"AllowSubtitleManagement": "לאפשר למשתמש הזה לערוך כתוביות",
"DeleteEntireSeries": "מחיקת {0} פרקים",
"DeleteSeries": "מחיקת סידרה",
"DeleteEpisode": "מחיקת פרק",
"DeleteName": "מחיקת {0}",
"SelectAudioNormalizationHelp": "עוצמת רצועת שמע - משנה את העוצמה של כל רצועה כך שהם יתנגנו בעוצמת קול זהה. עוצמת אלבום - משנה את העוצמה של כל רצועות השמע באלבום בלבד, תוך שמירה על התחום הדינמי של האלבום.",
"LabelAllowContentWithTags": "אפשר פריטים עם תגיות",
"EnableSmoothScroll": "הפעל גלילה חלקה",
"HeaderDeleteSeries": "מחק סדרה",
"LabelAlbumGain": "עוצמת אלבום",
"LabelDummyChapterDurationHelp": "מרווח הזמן בין פרקי הדמה. קבע ל-0 כדי להשבית את ייצור פרקי הדמה. שינוי זה לא ישפיע על פרקי דמה קיימים.",
"LabelMaxDaysForNextUpHelp": "הגדר את מספר הימים המקסימלי שתכנית צריכה להישאר ברשימת 'הבא בתור' מבלי לצפות בה.",
"LabelMaxVideoResolution": "רזולוצייה מקסימלית המותרת להמרת קידוד וידאו",
"LabelLocalCustomCss": "קוד CSS לעיצוב מותאם אישית ללקוח זה בלבד. ייתכן שתרצה להשבית את קוד ה-CSS המסופק על ידי השרת.",
"LabelMaxAudiobookResume": "דקות נותרות בספר המוקלט להמשך",
"LabelMaxAudiobookResumeHelp": "כותרים נחשבים כנוגנו במלואם כאשר משך הזמן הנותר קטן יותר מערך זה.",
"LabelMetadataReaders": "קוראי מטא-דאטה",
"LabelMetadataSavers": "שומרי מטא-דאטה"
} }

View file

@ -849,7 +849,7 @@
"Playlists": "Popisi za reprodukciju", "Playlists": "Popisi za reprodukciju",
"AllowMediaConversionHelp": "Dopusti ili odbij pristup mogućnosti konverzije medija.", "AllowMediaConversionHelp": "Dopusti ili odbij pristup mogućnosti konverzije medija.",
"AllLibraries": "Sve biblioteke", "AllLibraries": "Sve biblioteke",
"Aired": "Prenošeno", "Aired": "Emitirano",
"AirDate": "Datum prikazivanja", "AirDate": "Datum prikazivanja",
"AddedOnValue": "Dodano {0}", "AddedOnValue": "Dodano {0}",
"Songs": "Pjesme", "Songs": "Pjesme",
@ -1515,5 +1515,48 @@
"LabelSegmentKeepSecondsHelp": "Vrijeme u sekundama za koje se segmenti trebaju čuvati prije nego što se prebrišu. Mora biti veći od \"Throttle after\". Radi samo ako je omogućeno brisanje segmenta.", "LabelSegmentKeepSecondsHelp": "Vrijeme u sekundama za koje se segmenti trebaju čuvati prije nego što se prebrišu. Mora biti veći od \"Throttle after\". Radi samo ako je omogućeno brisanje segmenta.",
"LabelKnownProxies": "Poznati proxy-i", "LabelKnownProxies": "Poznati proxy-i",
"ButtonBackspace": "Backspace", "ButtonBackspace": "Backspace",
"LabelHomeScreenSectionValue": "{0}. odjeljak početne" "LabelHomeScreenSectionValue": "{0}. odjeljak početne",
"AllowContentWithTagsHelp": "Prikaži multimediju s barem jednom od odabranih oznaka.",
"ChannelResolutionSDPAL": "SD (PAL)",
"ConfirmDeleteSeries": "Brisanjem ovog serijala obrisat će se {0} epizoda s podatkovnog sistema i multimedijske biblioteke. Želite li nastaviti?",
"DeleteEpisode": "Obriši epizodu",
"DlnaMovedMessage": "DLNA funkcije su dostupne putem dodatka za Jellyfin.",
"LabelBuildVersion": "Verzija programa",
"LabelStereoDownmixAlgorithm": "Algoritam pretvorbe audio signala u stereo",
"LabelMaxAudiobookResume": "Preostalo vrijeme za nastavak audioknjige",
"LabelDummyChapterDurationHelp": "Interval između praznih poglavlja. Postaviti na 0 kako bi se onemogućilo stvaranje praznih poglavlja. Izmjena ove postavke nema učinka za postojeća prazna poglavlja.",
"LabelEnableAudioVbrHelp": "Promjenjiv broj bitova u sekundi nudi bolju kvalitetu naspram postavljanja prosječnog broja bitova u sekundi. U nekim rijetkim slučajevima ova postavka može prouzročiti učestalo učitavanje ili probleme s kompatibilnosti.",
"LabelLibraryPageSize": "Broj prikazanih stavki biblioteke",
"LabelServerVersion": "Verzija poslužitelja",
"LabelMinAudiobookResume": "Minimalno vrijeme za nastavak audio knjige u minutama",
"LabelSyncPlayNoGroups": "Nema dostupnih grupa",
"LabelSyncPlaySettingsSyncCorrectionHelp": "Omogući aktivnu sinkronizaciju izvođenja ubrzavanjem multimedije ili preskakivanjem sadržaja do određene pozicije. Onemogućite u slučaju jakog zastajkivanja.",
"LabelSyncPlaySettingsSkipToSyncHelp": "Metoda sinkronizacije koja preskače sadržaj do određene pozicije. Korekcija sinkronizacije mora biti omogućena.",
"LabelTonemappingPeakHelp": "Zamjeni vršnu vrijednost signala s ovom vrijednosti. Korisno kada metapodaci signala nisu pouzdani ili kada se tonalni raspon prilagođava s nižeg na viši raspon. Preporučene i pretpostavljene vrijednosti su 100 i 0.",
"LabelTranscodes": "Konverzija formata",
"LabelIsForced": "Nužni (titlovi)",
"AirPlay": "AirPlay",
"AllowSubtitleManagement": "Dopusti korisniku uređivanje titlova",
"BlockContentWithTagsHelp": "Ne prikazuj multimediju s barem jednom od odabranih oznaka.",
"ButtonEditUser": "Uredi korisnika",
"ChannelResolutionSD": "SD",
"ChannelResolutionHD": "HD",
"ChannelResolutionFullHD": "Full HD",
"ChannelResolutionUHD4K": "UHD (4K)",
"DeleteEntireSeries": "Obriši {0} epizoda",
"DeleteSeries": "Obriši serijal",
"DeleteName": "Obriši {0}",
"EnableSmoothScroll": "Omogući lakše pomicanje",
"HeaderDeleteSeries": "Obriši serijal",
"LabelAllowContentWithTags": "Dopusti stavke s oznakama",
"LabelBackdropScreensaverInterval": "Interval prikazivanja zaštite zaslona",
"LabelBackdropScreensaverIntervalHelp": "Vremeski raspon unutar kojeg se izmjenjuju pozadine prilikom korištenja zaštite zaslona (u sekundama).",
"LabelChapterImageResolutionHelp": "Rezolucija stvorenih sličica poglavlja. Izmjena ove postavke nema učinka na postojeća prazna poglavlja.",
"LabelEnableLUFSScanHelp": "Klijenti mogu normalizirati izvođenje zvučnog zapisa kako bi osigurali ujednačenu glasnoću između različitih zapisa. Ova postavka će produljiti trajanje popisivanja multimedije i koristit će više računalnih resursa.",
"LabelMaxVideoResolution": "Maksimalna dopuštena rezolucija za konvertiranje videa",
"LabelMediaDetails": "Detalji multimedije",
"LabelMaxMuxingQueueSize": "Maksimalna veličina reda čekanja za konvertiranje formata",
"LabelSyncPlaySettingsExtraTimeOffset": "Dodatni vremenski pomak",
"LabelTranscodingFramerate": "Broj sličica u sekundi prilikom konverzije formata",
"LabelTranscodingProgress": "Napredak konverzije formata"
} }

View file

@ -1397,7 +1397,7 @@
"LabelTonemappingRange": "Tónusleképezés tartomány", "LabelTonemappingRange": "Tónusleképezés tartomány",
"TonemappingAlgorithmHelp": "A tónusleképezés finomhangolható. Ha még nem ismered ezeket az opciókat, tartsd meg az alapértelmezett értéket. Az ajánlott érték 'BT.2390'.", "TonemappingAlgorithmHelp": "A tónusleképezés finomhangolható. Ha még nem ismered ezeket az opciókat, tartsd meg az alapértelmezett értéket. Az ajánlott érték 'BT.2390'.",
"LabelTonemappingAlgorithm": "Válaszd ki a használni kívánt tónusleképezési algoritmust", "LabelTonemappingAlgorithm": "Válaszd ki a használni kívánt tónusleképezési algoritmust",
"AllowTonemappingHelp": "Tone-mapping can transform the dynamic range of a video from HDR to SDR while maintaining image details and colors, which are very important information for representing the original scene. Currently works only with 10bit HDR10, HLG and DoVi videos. This requires the corresponding OpenCL or CUDA runtime.", "AllowTonemappingHelp": "",
"EnableTonemapping": "Tónusleképezés engedélyezése", "EnableTonemapping": "Tónusleképezés engedélyezése",
"LabelOpenclDeviceHelp": "Ez az OpenCL eszköz, amelyet a tónusleképezéshez használnak. A pont bal oldala a platform száma, a jobb oldala pedig a platformon található eszköz száma. Az alapértelmezett érték 0.0. Az OpenCL hardveres gyorsítási módszert tartalmazó FFmpeg alkalmazásfájl szükséges.", "LabelOpenclDeviceHelp": "Ez az OpenCL eszköz, amelyet a tónusleképezéshez használnak. A pont bal oldala a platform száma, a jobb oldala pedig a platformon található eszköz száma. Az alapértelmezett érték 0.0. Az OpenCL hardveres gyorsítási módszert tartalmazó FFmpeg alkalmazásfájl szükséges.",
"LabelOpenclDevice": "OpenCL eszköz", "LabelOpenclDevice": "OpenCL eszköz",
@ -1793,5 +1793,7 @@
"DlnaMovedMessage": "A DLNA funkció egy pluginba költözött.", "DlnaMovedMessage": "A DLNA funkció egy pluginba költözött.",
"LabelBuildVersion": "Build verzió", "LabelBuildVersion": "Build verzió",
"LabelServerVersion": "Szerver verzió", "LabelServerVersion": "Szerver verzió",
"AllowSubtitleManagement": "Feliratok szerkesztésének engedélyezése ennél a felhasználónál" "AllowSubtitleManagement": "Feliratok szerkesztésének engedélyezése ennél a felhasználónál",
"AllowContentWithTagsHelp": "Csak a megadott címkék legalább egyikével rendelkező médiát jeleníti meg.",
"AirPlay": "AirPlay"
} }

View file

@ -204,7 +204,7 @@
"Guide": "Guida", "Guide": "Guida",
"GuideProviderLogin": "Accedi", "GuideProviderLogin": "Accedi",
"GuideProviderSelectListings": "selezionare Annunci", "GuideProviderSelectListings": "selezionare Annunci",
"H264CrfHelp": "Il 'Fattore di Frequenza Costante' (CRF) è l'impostazione di qualità predefinita per l'encoder x264 e x265. È possibile impostare i valori compresi tra 0 e 51, in cui valori inferiori potrebbero determinare una migliore qualità (a discapito delle dimensioni superiori dei file). I valori normali sono compresi tra 18 e 28. L'impostazione predefinita per x264 è 23 e per x265 è 28, quindi è possibile utilizzare questo come punto di partenza.", "H264CrfHelp": "Il 'Fattore di Frequenza Costante' (CRF) è l'impostazione di qualità predefinita per gli encoder software x264 e x265. È possibile impostare i valori compresi tra 0 e 51, in cui valori inferiori potrebbero determinare una migliore qualità (a discapito delle dimensioni superiori dei file). I valori normali sono compresi tra 18 e 28. L'impostazione predefinita per x264 è 23 e per x265 è 28, quindi è possibile utilizzare questo come punto di partenza. Gli encoder hardware non usano queste impostazioni.",
"EncoderPresetHelp": "Selezionare una velocità maggiore per migliorare le performance, o minore per incrementare la qualità.", "EncoderPresetHelp": "Selezionare una velocità maggiore per migliorare le performance, o minore per incrementare la qualità.",
"HDPrograms": "Programmi HD", "HDPrograms": "Programmi HD",
"HardwareAccelerationWarning": "L'attivazione dell'accelerazione hardware potrebbe causare instabilità in qualche sistema. Assicurarsi che il sistema operativo e i driver video siano completamente aggiornati. Se hai difficoltà a riprodurre video dopo aver abilitato questa operazione, dovrai cambiare l'impostazione in None.", "HardwareAccelerationWarning": "L'attivazione dell'accelerazione hardware potrebbe causare instabilità in qualche sistema. Assicurarsi che il sistema operativo e i driver video siano completamente aggiornati. Se hai difficoltà a riprodurre video dopo aver abilitato questa operazione, dovrai cambiare l'impostazione in None.",
@ -1407,7 +1407,7 @@
"TonemappingRangeHelp": "Seleziona l'intervallo di colore in uscita. Auto imposta lo stesso del valore di entrata.", "TonemappingRangeHelp": "Seleziona l'intervallo di colore in uscita. Auto imposta lo stesso del valore di entrata.",
"LabelTonemappingRange": "Intervallo mappatura dei toni", "LabelTonemappingRange": "Intervallo mappatura dei toni",
"TonemappingAlgorithmHelp": "La mappatura dei toni può essere messa a punto. Se non sei abbastanza familiare con queste opzioni, lascia quelle predefinite. Il valore raccomandato è 'BT.2390'.", "TonemappingAlgorithmHelp": "La mappatura dei toni può essere messa a punto. Se non sei abbastanza familiare con queste opzioni, lascia quelle predefinite. Il valore raccomandato è 'BT.2390'.",
"AllowTonemappingHelp": "La mappatura dei toni può trasformare l'intervallo dinamico di un video da HDR a SDR mantenendo il dettaglio e i colori dell'immagine, che sono informazioni molto importanti per rappresentare la scena originale. Attualmente funziona solo con video HDR10, HLG e DoVi. Richiede le corrispondenti librerie OpenCL o CUDA.", "AllowTonemappingHelp": "La mappatura dei toni può trasformare l'intervallo dinamico di un video da HDR a SDR mantenendo il dettaglio e i colori dell'immagine, che sono informazioni molto importanti per rappresentare la scena originale. Attualmente funziona solo con video HDR10, HLG e DoVi. Richiede le corrispondenti librerie GPGPU.",
"LabelOpenclDeviceHelp": "Questo è il dispositivo OpenCL utilizzato per la mappatura dei toni. Il lato sinistro del punto è il numero di piattaforma, mentre la parte destra è il numero del dispositivo sulla piattaforma. Il valore base è 0.0. Il file di applicazione FFmpeg contenente il metodo di accelerazione hardware OpenCL è richiesto.", "LabelOpenclDeviceHelp": "Questo è il dispositivo OpenCL utilizzato per la mappatura dei toni. Il lato sinistro del punto è il numero di piattaforma, mentre la parte destra è il numero del dispositivo sulla piattaforma. Il valore base è 0.0. Il file di applicazione FFmpeg contenente il metodo di accelerazione hardware OpenCL è richiesto.",
"LabelColorPrimaries": "Primari colore", "LabelColorPrimaries": "Primari colore",
"LabelColorTransfer": "Trasferimento colore", "LabelColorTransfer": "Trasferimento colore",
@ -1640,7 +1640,7 @@
"ButtonClose": "Chiudi", "ButtonClose": "Chiudi",
"Production": "Produzione", "Production": "Produzione",
"MessageUnauthorizedUser": "Non sei autorizzato ad accedere al server in questo momento. Contatta l'amministratore del server per ulteriori dettagli.", "MessageUnauthorizedUser": "Non sei autorizzato ad accedere al server in questo momento. Contatta l'amministratore del server per ulteriori dettagli.",
"EnableEnhancedNvdecDecoderHelp": "Implementazione sperimentale NVDEC, da abilitare solo se esistono errori di decodifica.", "EnableEnhancedNvdecDecoderHelp": "Implementazione NVDEC avanzata, disabilitare questa opzione per utilizzare CUVID se si verificano errori di decodifica.",
"Sample": "Prova", "Sample": "Prova",
"Trailer": "Trailer", "Trailer": "Trailer",
"HomeVideosPhotos": "Home Video e Foto", "HomeVideosPhotos": "Home Video e Foto",
@ -1789,7 +1789,7 @@
"ButtonEditUser": "Modifica utente", "ButtonEditUser": "Modifica utente",
"DlnaMovedMessage": "La funzionalità DLNA è stata spostata su ad un plugin.", "DlnaMovedMessage": "La funzionalità DLNA è stata spostata su ad un plugin.",
"EnableVideoToolboxTonemapping": "Abilita mapping VideoToolbox Tone", "EnableVideoToolboxTonemapping": "Abilita mapping VideoToolbox Tone",
"AllowVideoToolboxTonemappingHelp": "Mapping del tono accellerato con hardware procurato da VideoToolbox. Funziona con la maggior parte dei formati HDR, inclusi HDR10, HDR10+, e HLG, ma non funziona con Doly Vision Profile 5. Questo ha una priorità più alta rispetto a un'altra implementazione OpenCL.", "AllowVideoToolboxTonemappingHelp": "Mapping del tono accellerato con hardware procurato da VideoToolbox. Funziona con la maggior parte dei formati HDR, inclusi HDR10, HDR10+, e HLG, ma non funziona con Doly Vision Profile 5. Questo ha una priorità più alta rispetto a un'altra implementazione Metal.",
"ChannelResolutionSD": "SD", "ChannelResolutionSD": "SD",
"ChannelResolutionSDPAL": "SD (PAL)", "ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionHD": "HD", "ChannelResolutionHD": "HD",
@ -1798,5 +1798,74 @@
"LabelUseReplayGainTags": "Usa i tag ReplayGain", "LabelUseReplayGainTags": "Usa i tag ReplayGain",
"AllowSubtitleManagement": "Permetti a questo utente di modificare i sottotitoli", "AllowSubtitleManagement": "Permetti a questo utente di modificare i sottotitoli",
"DeleteName": "Elimina {0}", "DeleteName": "Elimina {0}",
"LabelUseReplayGainTagsHelp": "Scansiona i file audio per i tag di replaygain e usali invece di calcolare il valore LUFS. (Utilizza meno potenza di calcolo. Sostituirà l'opzione \"Scansione LUFS\")" "LabelUseReplayGainTagsHelp": "Scansiona i file audio per i tag di replaygain e usali invece di calcolare il valore LUFS. (Utilizza meno potenza di calcolo. Sostituirà l'opzione \"Scansione LUFS\")",
"PlaybackError.FATAL_HLS_ERROR": "Si è verificato un errore nel flusso HLS.",
"PlaybackError.ASS_RENDER_ERROR": "Si è verificato un errore nel rendering dei sottotitoli ASS/SSA.",
"PlaybackError.MEDIA_NOT_SUPPORTED": "La riproduzione non è riuscita perché il file non è supportato da questo client.",
"PlaybackError.MEDIA_DECODE_ERROR": "La riproduzione non è riuscita a causa di un errore di decodifica.",
"PlaybackError.NETWORK_ERROR": "La riproduzione non è riuscita a causa di un errore di rete.",
"PlaybackError.NO_MEDIA_ERROR": "Impossibile trovare una sorgente multimediale valida da riprodurre.",
"PlaybackError.PLAYER_ERROR": "La riproduzione non è riuscita a causa di un errore del media player.",
"PlaybackError.SERVER_ERROR": "La riproduzione non è riuscita a causa di un errore del server.",
"PlaybackError.RateLimitExceeded": "Questo file non può essere riprodotto in questo momento a causa dei limiti di banda.",
"PriorityNormal": "Normale",
"PriorityBelowNormal": "Sotto il normale",
"PriorityIdle": "Inattivo",
"LabelProcessPriority": "Priorità dei Processi",
"ConfirmDeleteSeries": "Cancellare questa serie comporta l'eliminazione di TUTTI i {0} episodi dal file system e dalla libreria multimediale. Siete sicuri di voler continuare?",
"DeleteEntireSeries": "Elimina {0} Episodi",
"DeleteSeries": "Elimina la Serie TV",
"DeleteEpisode": "Elimina Episodio",
"HeaderDeleteSeries": "Elimina Serie TV",
"AllowMjpegEncoding": "Consentire la codifica in formato MJPEG (utilizzato durante la modalità trick)",
"LabelTrickplayAccel": "Abilitare l'accelerazione hardware",
"LabelTrickplayAccelHelp": "Assicurarsi di attivare \"Consenti codifica MJPEG\" in Transcodifica se l'hardware lo supporta.",
"NonBlockingScan": "Non bloccante - mette in coda la generazione, poi la restituisce",
"BlockingScan": "Bloccante - accoda la generazione, blocca la scansione fino al suo completamento",
"LabelScanBehavior": "Comportamento di scansione",
"PriorityHigh": "Alta",
"PriorityAboveNormal": "Sopra il normale",
"EnableSmoothScroll": "Abilita scorrimento fluido",
"LabelAllowContentWithTags": "Consentire elementi con tag",
"Trickplay": "Trickplay",
"LabelProcessPriorityHelp": "L'impostazione di un valore più basso o più alto determina il modo in cui la CPU dà priorità al processo di generazione di ffmpeg trickplay rispetto ad altri processi. Se si nota un rallentamento durante la generazione di immagini trickplay ma non si vuole interrompere completamente la loro generazione, provare a ridurre questo parametro e il numero di thread.",
"Lyric": "Lyric",
"PlaybackError.NotAllowed": "La riproduzione di questo file non è consentita.",
"LabelScanBehaviorHelp": "Il comportamento predefinito è quello non bloccante, che aggiunge i media alla libreria prima che venga generato il trickplay. Il blocco assicura che i file di trickplay siano generati prima che i media siano aggiunti alla libreria, ma rende le scansioni significativamente più lunghe.",
"AllowContentWithTagsHelp": "Mostra solo i media con almeno uno dei tag specificati.",
"AirPlay": "AirPlay",
"BlockContentWithTagsHelp": "Nascondi i media con almeno uno dei tag specificati.",
"LimitSupportedVideoResolution": "Limitare la risoluzione video massima supportata",
"LimitSupportedVideoResolutionHelp": "Utilizzare 'Risoluzione massima consentita per la transcodifica video' come risoluzione video massima supportata.",
"LabelTileHeightHelp": "Numero massimo di immagini per riquadro in direzione Y.",
"LabelJpegQualityHelp": "La qualità di compressione JPEG per le immagini di trickplay.",
"LabelQscale": "Qscale",
"OptionExtractTrickplayImage": "Abilita l'estrazione dell'immagine di trickplay",
"LabelWidthResolutions": "Risoluzioni in larghezza",
"LabelEncodingFormatOptions": "Opzioni del formato di codifica",
"LabelImageInterval": "Intervallo di immagine",
"ExtractTrickplayImagesHelp": "Le immagini Trickplay sono simili alle immagini dei capitoli, ma coprono l'intera lunghezza del contenuto e vengono utilizzate per mostrare un'anteprima durante lo scorrimento dei video.",
"LabelTrickplayThreadsHelp": "Il numero di thread da passare all'argomento '-threads' di ffmpeg.",
"LabelExtractTrickplayDuringLibraryScanHelp": "Genera le immagini di trickplay quando i video vengono importati durante la scansione della libreria. Altrimenti, verranno estratte durante l'attività programmata delle immagini di trickplay. Se la generazione è impostata su non bloccante, questo non influisce sul tempo di completamento della scansione della libreria.",
"LabelImageIntervalHelp": "Intervallo di tempo (ms) tra ogni nuova immagine di trickplay.",
"LabelJpegQuality": "Qualità JPEG",
"LabelExtractTrickplayDuringLibraryScan": "Estrarre le immagini dei trucchi durante la scansione della libreria",
"LabelTileWidthHelp": "Numero massimo di immagini per riquadro nella direzione X.",
"LabelTileWidth": "Larghezza Riquadro",
"LabelQscaleHelp": "La scala di qualità delle immagini prodotte da ffmpeg, con 2 come qualità massima e 31 come qualità minima.",
"LabelTrickplayThreads": "Thread di FFmpeg",
"EnableLibrary": "Abilita la libreria",
"EnableLibraryHelp": "Disabilitando la libreria, la si nasconde da tutte le visualizzazioni degli utenti.",
"EncodingFormatHelp": "Selezionare la codifica video in cui Jellyfin deve eseguire la transcodifica. Jellyfin utilizzerà la codifica software quando l'accelerazione hardware per il formato selezionato non è disponibile. La codifica H264 sarà sempre abilitata.",
"LabelWidthResolutionsHelp": "Elenco separato da virgole delle larghezze (px) con cui saranno generate le immagini di trickplay. Tutte le immagini devono essere generate in modo proporzionale alla sorgente, quindi una larghezza di 320 su un video 16:9 finisce per essere circa 320x180.",
"LabelTileHeight": "Altezza Riquadro",
"LabelTrickplayAccelEncoding": "Abilitare l'accelerazione hardware della codifica MJPEG",
"LabelTrickplayAccelEncodingHelp": "Disponibile solo per QSV e VAAPI, questa opzione non ha alcun effetto sugli altri metodi di accelerazione hardware.",
"ConfirmDeleteLyrics": "Cancellare questi testi li rimuoverà sia dalle cartelle del sistema che dalla tua libreria multimediale. Sei sicuro di continuare?",
"DeleteLyrics": "Cancella testi",
"ErrorDeletingLyrics": "C'è stato un errore nel cancellare i testi dal server. Perfavore controlla che Jellyfin abbia accesso alla scrittura della cartella dei media e riprova.",
"HeaderDeleteLyrics": "Cancella Testi",
"HeaderNoLyrics": "Nessun testo trovato",
"Lyrics": "Testi",
"ViewLyrics": "Visualizza testi"
} }

View file

@ -40,5 +40,16 @@
"AirDate": "ეთერში გაშვების თარიღი", "AirDate": "ეთერში გაშვების თარიღი",
"AllLibraries": "ყველა ბიბლიოთეკა", "AllLibraries": "ყველა ბიბლიოთეკა",
"AllowedRemoteAddressesHelp": "IP მისამართების ან IP/ქსელური მასკის ჩანაწერების ძიმეთი დაყოფილი სია ქსელებისთვის, რომლებიც შეძლებენ დაშორებულად დაკავშირებას. თუ დატოვებთ ცარიელს, ყველა დაშორებული მისამართი იქნება დაშვებული.", "AllowedRemoteAddressesHelp": "IP მისამართების ან IP/ქსელური მასკის ჩანაწერების ძიმეთი დაყოფილი სია ქსელებისთვის, რომლებიც შეძლებენ დაშორებულად დაკავშირებას. თუ დატოვებთ ცარიელს, ყველა დაშორებული მისამართი იქნება დაშვებული.",
"AllowCollectionManagement": "ამ მომხმარებლისთვის კოლექციების მართვის საშუალების მიცემა" "AllowCollectionManagement": "ამ მომხმარებლისთვის კოლექციების მართვის საშუალების მიცემა",
"AllowContentWithTagsHelp": "მხოლოდ იმ მედია ფაილების ჩვენება, რომლებსაც ერთი ან მეტი იარლიყი გააჩნიათ.",
"AllowSubtitleManagement": "ამ მომხმარებლისთვის სუბტიტრების შეცვლის საშუალების მიცემა",
"AllowFfmpegThrottling": "ტრანსკოდირების შეფერხება",
"AllowFfmpegThrottlingHelp": "ნაკლები რესურსის გამოყენების მიზნით პროცესის შეჩერება, როდეცას ტრანსკოდირება ან remux საკმარისად გაცდება ჩვენების მიმდინარე პოზიციას. ეს ყველაზე გამოსადეგია იშვიათად გადახვევისას. გამორთეთ ეს ფუნქცია თუ კი ჩვენების პრობლემები გაგაჩნიათ.",
"AllowSegmentDeletion": "მონაკვეთების წაშლა",
"AllowSegmentDeletionHelp": "მონაკვეთის წაშლა, კლიენტის მიერ მისი გადმოწერის შემდგომ. ეს თავს აარიდებს სრული ტრანსკოდირებული ფაილების დისკზე შენახვას. გამორთეთ ეს ფუნქცია თუ კი ჩვენების პრობლემები გაგაჩნიათ.",
"LabelSegmentKeepSecondsHelp": "წამთა ოდენობა, რომელიც განსაზღვრავს რამდენი ხნით შეინახება მონაკვეთები კლიენტის მიერ მისი გადმოწერის შემდგომ. მუშობს მხოლოდ მაშინ, როდესაც მონაკვეთების წაშლის ფუნქცია ჩართულია.",
"LabelThrottleDelaySecondsHelp": "წამთა ოდენობა, რის შემდეგაც ტრანსკოდირება იქნება შეფერხებული. უნდა იყოს საკმარისად ხანგრძლივი, იმისთვის რომ, კლიენტმა შეინარჩუნოს ჯანსაღი ბუფერი. მუშაობს მხოლოდ მაშინ, როდესაც შეფერხების ფუნქცია ჩართულია.",
"LabelSegmentKeepSeconds": "მითითებული დროის განმავლობაში მონაკვეთები შეინახება",
"AirPlay": "ეარფლეი",
"LabelThrottleDelaySeconds": "შემდეგ"
} }

View file

@ -851,8 +851,8 @@
"LabelSubtitleDownloaders": "자막 다운로더", "LabelSubtitleDownloaders": "자막 다운로더",
"LabelStopping": "중지", "LabelStopping": "중지",
"LabelSportsCategories": "스포츠 카테고리", "LabelSportsCategories": "스포츠 카테고리",
"LabelSkipBackLength": "뒤로 건너기 길이", "LabelSkipBackLength": "뒤로 건너기 길이",
"LabelSkipForwardLength": "앞으로 건너기 길이", "LabelSkipForwardLength": "앞으로 건너기 길이",
"LabelSimultaneousConnectionLimit": "동시 스트림 제한", "LabelSimultaneousConnectionLimit": "동시 스트림 제한",
"LabelSize": "크기", "LabelSize": "크기",
"LabelServerName": "서버 이름", "LabelServerName": "서버 이름",
@ -1773,5 +1773,6 @@
"MachineTranslated": "기계 번역", "MachineTranslated": "기계 번역",
"ForeignPartsOnly": "강제/외부 파트만", "ForeignPartsOnly": "강제/외부 파트만",
"HearingImpairedShort": "청각장애/SDH", "HearingImpairedShort": "청각장애/SDH",
"HeaderGuestCast": "게스트" "HeaderGuestCast": "게스트",
"AirPlay": "AirPlay"
} }

View file

@ -189,7 +189,7 @@
"LabelDateAdded": "Pievienošanas datums", "LabelDateAdded": "Pievienošanas datums",
"LabelCurrentPassword": "Pašreizējā parole", "LabelCurrentPassword": "Pašreizējā parole",
"LabelCriticRating": "Kritiķu reitings", "LabelCriticRating": "Kritiķu reitings",
"LabelCountry": "Valsts", "LabelCountry": "Valsts/Reģions",
"LabelContentType": "Satura veids", "LabelContentType": "Satura veids",
"LabelCommunityRating": "Kopienas reitings", "LabelCommunityRating": "Kopienas reitings",
"LabelCollection": "Kolekcija", "LabelCollection": "Kolekcija",
@ -602,7 +602,7 @@
"TitleHardwareAcceleration": "Aparatūras Paātrināšana", "TitleHardwareAcceleration": "Aparatūras Paātrināšana",
"Thursday": "Ceturtdiena", "Thursday": "Ceturtdiena",
"ThemeVideos": "Tēmas video", "ThemeVideos": "Tēmas video",
"ThemeSongs": "Tēmas mūzika", "ThemeSongs": "Motīvu dziesmas",
"TellUsAboutYourself": "Pastāsti mums par sevi", "TellUsAboutYourself": "Pastāsti mums par sevi",
"TagsValue": "Birkas: {0}", "TagsValue": "Birkas: {0}",
"Tags": "Birkas", "Tags": "Birkas",
@ -1048,7 +1048,7 @@
"Data": "Dati", "Data": "Dati",
"ButtonUseQuickConnect": "Izmantot Quick Connect", "ButtonUseQuickConnect": "Izmantot Quick Connect",
"ButtonActivate": "Aktivizēt", "ButtonActivate": "Aktivizēt",
"BoxSet": "", "BoxSet": "Mēdiju kopas",
"Authorize": "Autorizēt", "Authorize": "Autorizēt",
"Filter": "Filtrs", "Filter": "Filtrs",
"Features": "Īpašības", "Features": "Īpašības",
@ -1070,7 +1070,7 @@
"EnableDetailsBanner": "Informācijas reklāmkarogs", "EnableDetailsBanner": "Informācijas reklāmkarogs",
"ButtonPlayer": "Atskaņotājs", "ButtonPlayer": "Atskaņotājs",
"ButtonCast": "Raidīt uz ierīci", "ButtonCast": "Raidīt uz ierīci",
"AllowTonemappingHelp": "Tonālā kartēšana var pārveidot video dinamisko diapazonu no HDR uz SDR, vienlaikus saglabājot attēla detaļas un krāsas, kas ir ļoti svarīga informācija, lai atveidotu sākotnējo ainu. Pašlaik darbojas tikai ar 10bit HDR10, HLG un DoVi video. Šim nolūkam ir nepieciešama atbilstoša OpenCL vai CUDA izpildmehānisma programma.", "AllowTonemappingHelp": "Tonālā kartēšana var pārveidot video dinamisko diapazonu no HDR uz SDR, vienlaikus saglabājot attēla detaļas un krāsas, kas ir ļoti svarīga informācija, lai atveidotu sākotnējo ainu. Pašlaik darbojas tikai ar 10bit HDR10, HLG un DoVi video. Šim nolūkam ir nepieciešama atbilstoša GPGPU izpildmehānisma programma.",
"LabelChromecastVersion": "Google Cast versija", "LabelChromecastVersion": "Google Cast versija",
"HeaderUploadSubtitle": "Augšupielādēt subtitrus", "HeaderUploadSubtitle": "Augšupielādēt subtitrus",
"HeaderRemoteAccessSettings": "Attālinātas pieejas iestatījumi", "HeaderRemoteAccessSettings": "Attālinātas pieejas iestatījumi",
@ -1223,7 +1223,7 @@
"HeaderContainerProfileHelp": "Konteineru profili norāda uz ierīces ierobežojumiem, atskaņojot noteiktus formātus. Ja tiek piemērots ierobežojums, multivide tiks transkodēta, pat ja formāts ir konfigurēts tiešai atskaņošanai.", "HeaderContainerProfileHelp": "Konteineru profili norāda uz ierīces ierobežojumiem, atskaņojot noteiktus formātus. Ja tiek piemērots ierobežojums, multivide tiks transkodēta, pat ja formāts ir konfigurēts tiešai atskaņošanai.",
"HeaderCodecProfileHelp": "Kodeku profili norāda uz ierīces ierobežojumiem, atskaņojot noteiktus kodekus. Ja tiek piemērots ierobežojums, fails tiks transkodēts pat tad, ja kodeks ir konfigurēts tiešai atskaņošanai.", "HeaderCodecProfileHelp": "Kodeku profili norāda uz ierīces ierobežojumiem, atskaņojot noteiktus kodekus. Ja tiek piemērots ierobežojums, fails tiks transkodēts pat tad, ja kodeks ir konfigurēts tiešai atskaņošanai.",
"HeaderAutoDiscovery": "Tīkla atklāšana", "HeaderAutoDiscovery": "Tīkla atklāšana",
"H264CrfHelp": "Constant Rate Factor (CRF) ir noklusējuma kvalitātes iestatījums priekš x264 un x265 kodētājiem. Pieļaujamās vērtības 0-51, kur zemāka vērtības atbilst labākai kvalitātei (jo mazāks skaitlis jo lielāki faili). Parasti izvēlas 18-28. Noklusējums x264 kodētājam ir 23, x265 ir 28. Noklusējuma vērtības var izmantot kā sākuma punktu.", "H264CrfHelp": "Constant Rate Factor (CRF) ir noklusējuma kvalitātes iestatījums priekš x264 un x265 programmatūras kodētājiem. Pieļaujamās vērtības 0-51, kur zemāka vērtības atbilst labākai kvalitātei (jo mazāks skaitlis jo lielāki faili). Parasti izvēlas 18-28. Noklusējums x264 kodētājam ir 23, x265 ir 28. Noklusējuma vērtības var izmantot kā sākuma punktu. Aparatūras kodētāji neizmanto šos iestatījumus.",
"GuideProviderSelectListings": "Izvēlēties sarakstus", "GuideProviderSelectListings": "Izvēlēties sarakstus",
"ErrorPlayerNotFound": "Atskaņotājs pieprasītajai multividei nav atrasts.", "ErrorPlayerNotFound": "Atskaņotājs pieprasītajai multividei nav atrasts.",
"ErrorAddingListingsToSchedulesDirect": "Notika kļūda, pievienojot sarakstu jūsu Schedules Direct kontā. Schedules Direct pieļauj vienā kontā tikai ierobežotu sarakstu skaitu. Pirms turpināt, jums būtu jāpiesakās Schedules Direct vietnē un jānoņem citi saraksti no sava konta.", "ErrorAddingListingsToSchedulesDirect": "Notika kļūda, pievienojot sarakstu jūsu Schedules Direct kontā. Schedules Direct pieļauj vienā kontā tikai ierobežotu sarakstu skaitu. Pirms turpināt, jums būtu jāpiesakās Schedules Direct vietnē un jānoņem citi saraksti no sava konta.",
@ -1234,7 +1234,7 @@
"Conductor": "Diriģents", "Conductor": "Diriģents",
"Casual": "Ikdienišķs", "Casual": "Ikdienišķs",
"Arranger": "Aranžetājs", "Arranger": "Aranžetājs",
"AgeValue": "({0} gadi)", "AgeValue": "({0} gadu vecs)",
"LabelPublishedServerUriHelp": "Pārrakstīt URI, ko izmanto Jellyfin, vadoties pēc tīkla interfeisa vai klienta IP adreses.", "LabelPublishedServerUriHelp": "Pārrakstīt URI, ko izmanto Jellyfin, vadoties pēc tīkla interfeisa vai klienta IP adreses.",
"LabelPublishedServerUri": "Publicētā servera URI", "LabelPublishedServerUri": "Publicētā servera URI",
"LabelProtocolInfoHelp": "Vērtība, kas tiks izmantota, atbildot uz GetProtocolInfo pieprasījumiem no ierīces.", "LabelProtocolInfoHelp": "Vērtība, kas tiks izmantota, atbildot uz GetProtocolInfo pieprasījumiem no ierīces.",
@ -1434,7 +1434,7 @@
"OptionMaxActiveSessions": "Iestatiet maksimālo vienlaicīgo šī lietotāja sesiju skaitu.", "OptionMaxActiveSessions": "Iestatiet maksimālo vienlaicīgo šī lietotāja sesiju skaitu.",
"Print": "Drukāt", "Print": "Drukāt",
"QuickConnect": "Quick Connect", "QuickConnect": "Quick Connect",
"OptionHasThemeSong": "Tēmas mūzika", "OptionHasThemeSong": "Motīvu dziesma",
"QuickConnectAuthorizeFail": "Nezināms Quick Connect kods", "QuickConnectAuthorizeFail": "Nezināms Quick Connect kods",
"PlaceFavoriteChannelsAtBeginning": "Iecienītāko kanālu ievietošana sākumā", "PlaceFavoriteChannelsAtBeginning": "Iecienītāko kanālu ievietošana sākumā",
"OptionDisplayFolderView": "Rādīt mapju skatījumu, lai parādītu vienkāršas multivides mapes", "OptionDisplayFolderView": "Rādīt mapju skatījumu, lai parādītu vienkāršas multivides mapes",
@ -1482,7 +1482,7 @@
"PreferEmbeddedTitlesOverFileNamesHelp": "Noteikt redzamo nosaukumu, kas jāizmanto, ja nav pieejami interneta metadati vai vietējie metadati.", "PreferEmbeddedTitlesOverFileNamesHelp": "Noteikt redzamo nosaukumu, kas jāizmanto, ja nav pieejami interneta metadati vai vietējie metadati.",
"OptionSpecialEpisode": "Speciālizlaidumi", "OptionSpecialEpisode": "Speciālizlaidumi",
"AllowSegmentDeletion": "Dzēst segmentus", "AllowSegmentDeletion": "Dzēst segmentus",
"AllowSegmentDeletionHelp": "Dzēst vecus segmentus pēc to nosūtīšanas klientam. Šis ļauj neglabāt visu transkodēto failu diskā. Izslēdziet šo tikai ja jums ir problēmas ar atskaņošanu.", "AllowSegmentDeletionHelp": "Dzēst vecus segmentus pēc tam, kad to ir lejupielādējis klients. Šis ļauj neglabāt visu transkodēto failu diskā. Izslēdziet šo tikai ja jums ir problēmas ar atskaņošanu.",
"GoHome": "Doties uz sākumu", "GoHome": "Doties uz sākumu",
"HeaderEpisodesStatus": "Sēriju statuss", "HeaderEpisodesStatus": "Sēriju statuss",
"LabelDate": "Datums", "LabelDate": "Datums",
@ -1651,7 +1651,7 @@
"LabelSelectAudioChannels": "Kanāli", "LabelSelectAudioChannels": "Kanāli",
"SyncPlayGroupDefaultTitle": "{0} grupa", "SyncPlayGroupDefaultTitle": "{0} grupa",
"ValueConditions": "Nosacījumi: {0}", "ValueConditions": "Nosacījumi: {0}",
"LabelSegmentKeepSecondsHelp": "Laiks sekundēs, cik ilgi jāsaglabā segmenti, pirms tie tiek pārrakstīti. Jābūt lielākam par “Bremzēt pēc”. Darbojas tikai tad, ja ir iespējota segmentu dzēšana.", "LabelSegmentKeepSecondsHelp": "Laiks sekundēs, cik ilgi jāsaglabā segmenti pēc tam, kad to ir lejupielādējis klients. Darbojas tikai tad, ja ir iespējota segmentu dzēšana.",
"EnableEnhancedNvdecDecoder": "Iespējot uzlabotu NVDEC dekoderi", "EnableEnhancedNvdecDecoder": "Iespējot uzlabotu NVDEC dekoderi",
"Sample": "Paraugs", "Sample": "Paraugs",
"Saturday": "Sestdiena", "Saturday": "Sestdiena",
@ -1695,7 +1695,7 @@
"RememberSubtitleSelectionsHelp": "Mēģināt iestatīt subtitru celiņu, kas pēc iespējas līdzinātos pēdējā video celiņam.", "RememberSubtitleSelectionsHelp": "Mēģināt iestatīt subtitru celiņu, kas pēc iespējas līdzinātos pēdējā video celiņam.",
"VideoFramerateNotSupported": "Video kadru ātrums netiek atbalstīts", "VideoFramerateNotSupported": "Video kadru ātrums netiek atbalstīts",
"Uniform": "Viendabīga", "Uniform": "Viendabīga",
"ThemeSong": "Tēmas mūzika", "ThemeSong": "Motīvu dziesma",
"Smaller": "Mazāks", "Smaller": "Mazāks",
"LabelEnableGamepad": "Iespējot Gamepad", "LabelEnableGamepad": "Iespējot Gamepad",
"ThemeVideo": "Tēmas video", "ThemeVideo": "Tēmas video",
@ -1721,5 +1721,30 @@
"LabelAllowedAudioChannels": "Maksimāli atļautie audio kanāli", "LabelAllowedAudioChannels": "Maksimāli atļautie audio kanāli",
"LabelFallbackFontPath": "Rezerves fonta mapes ceļš", "LabelFallbackFontPath": "Rezerves fonta mapes ceļš",
"HeaderAllRecordings": "Visi ieraksti", "HeaderAllRecordings": "Visi ieraksti",
"Interview": "Intervija" "Interview": "Intervija",
"AirPlay": "AirPlay",
"ButtonEditUser": "Rediģēt lietotāju",
"ChannelResolutionSD": "SD",
"ChannelResolutionSDPAL": "SD (PAL)",
"ChannelResolutionFullHD": "Full HD",
"ChannelResolutionUHD4K": "UHD (4K)",
"DeleteName": "Dzēst {0}",
"EnableSmoothScroll": "Iespējot vienmērīgu ritināšanu",
"AllowContentWithTagsHelp": "Rādīt multimediju tikai ar viss maz vienu norādītu birku.",
"BlockContentWithTagsHelp": "Paslēpt multimediju ar viss maz vienu no norādītajām birkām.",
"ChannelResolutionHD": "HD",
"DeleteSeries": "Dzēst sēriju",
"DeleteEpisode": "Dzēst epizodi",
"LabelAllowContentWithTags": "Atļaut vienumus ar birkām",
"LabelAlbumGain": "Albuma pastiprinājums",
"LabelServerVersion": "Servera versija",
"ConfirmDeleteSeries": "Dzēšot šo sēriju, tiks izdzēstas VISAS {0} sērijas gan no failu sistēmas, gan no multivides bibliotēkas. Vai tiešām vēlaties turpināt?",
"DeleteEntireSeries": "Dzēst {0} sērijas",
"DlnaMovedMessage": "DLNA funkcionalitāte ir pārvietota uz spraudni.",
"HeaderDeleteSeries": "Dzēst sēriju",
"EnableLibrary": "Iespējot bibliotēku",
"EnableLibraryHelp": "Atspējojot bibliotēku, tā tiks paslēpta no visiem lietotājiem.",
"AllowSubtitleManagement": "Atļaut šim lietotājam rediģēt subtitrus",
"LabelBuildVersion": "Kompilācijas versija",
"SelectAudioNormalizationHelp": "Audioceliņa pastiprinājums — pielāgo katra celiņa skaļumu, lai tie tiktu atskaņoti ar tādu pašu skaļumu. Albuma pastiprinājums - pielāgo visus albuma audio, saglabājot albuma dinamisko diapazonu."
} }

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