mirror of https://github.com/jellyfin/jellyfin.git
Merge branch 'master' into feat/book-persons
This commit is contained in:
commit
eb2bcc91c5
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-ef": {
|
||||||
|
"version": "7.0.13",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-ef"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,18 +20,18 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||||
with:
|
with:
|
||||||
dotnet-version: '7.0.x'
|
dotnet-version: '7.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
|
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
|
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@2cb752a87e96af96708ab57187ab6372ee1973ab # v2.22.0
|
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
@ -39,7 +39,7 @@ jobs:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||||
|
@ -112,7 +112,7 @@ jobs:
|
||||||
direction: last
|
direction: last
|
||||||
body-includes: openapi-diff-workflow-comment
|
body-includes: openapi-diff-workflow-comment
|
||||||
- name: Reply or edit difference comment (changed)
|
- name: Reply or edit difference comment (changed)
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ steps.read-diff.outputs.body != '' }}
|
if: ${{ steps.read-diff.outputs.body != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
@ -127,7 +127,7 @@ jobs:
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
- name: Edit difference comment (unchanged)
|
- name: Edit difference comment (unchanged)
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
if: ${{ steps.read-diff.outputs.body == '' && steps.find-comment.outputs.comment-id != '' }}
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
|
@ -0,0 +1,50 @@
|
||||||
|
name: Tests
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
# Run tests against the forked branch, but
|
||||||
|
# do not allow access to secrets
|
||||||
|
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflows-in-forked-repositories
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
SDK_VERSION: "7.0.x"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run-tests:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||||
|
|
||||||
|
runs-on: "${{ matrix.os }}"
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
|
||||||
|
|
||||||
|
- uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3
|
||||||
|
with:
|
||||||
|
dotnet-version: ${{ env.SDK_VERSION }}
|
||||||
|
|
||||||
|
- name: Run DotNet CLI Tests
|
||||||
|
run: >
|
||||||
|
dotnet test Jellyfin.sln
|
||||||
|
--configuration Release
|
||||||
|
--collect:"XPlat Code Coverage"
|
||||||
|
--settings tests/coverletArgs.runsettings
|
||||||
|
--verbosity minimal
|
||||||
|
|
||||||
|
- name: Merge code coverage results
|
||||||
|
uses: danielpalme/ReportGenerator-GitHub-Action@873ee34c88a6234bdab7fd264d3666fd1ab417f7 # 5
|
||||||
|
with:
|
||||||
|
reports: "**/coverage.cobertura.xml"
|
||||||
|
targetdir: "merged/"
|
||||||
|
reporttypes: "Cobertura"
|
||||||
|
|
||||||
|
# TODO - which action / tool to use to publish code coverage results?
|
||||||
|
# - name: Publish code coverage results
|
||||||
|
|
||||||
|
- name: Publish OpenAPI Artifact
|
||||||
|
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3
|
||||||
|
with:
|
||||||
|
name: "OpenAPI Spec"
|
||||||
|
path: "tests/Jellyfin.Server.Integration.Tests/bin/Release/net*/openapi.json"
|
|
@ -17,14 +17,14 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify as seen
|
- name: Notify as seen
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
comment-id: ${{ github.event.comment.id }}
|
comment-id: ${{ github.event.comment.id }}
|
||||||
reactions: '+1'
|
reactions: '+1'
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
@ -43,7 +43,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Notify as seen
|
- name: Notify as seen
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ github.event.comment != null }}
|
if: ${{ github.event.comment != null }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -51,14 +51,14 @@ jobs:
|
||||||
reactions: eyes
|
reactions: eyes
|
||||||
|
|
||||||
- name: Checkout the latest code
|
- name: Checkout the latest code
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Notify as running
|
- name: Notify as running
|
||||||
id: comment_running
|
id: comment_running
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ github.event.comment != null }}
|
if: ${{ github.event.comment != null }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -93,7 +93,7 @@ jobs:
|
||||||
exit ${retcode}
|
exit ${retcode}
|
||||||
|
|
||||||
- name: Notify with result success
|
- name: Notify with result success
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ github.event.comment != null && success() }}
|
if: ${{ github.event.comment != null && success() }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
@ -108,7 +108,7 @@ jobs:
|
||||||
reactions: hooray
|
reactions: hooray
|
||||||
|
|
||||||
- name: Notify with result failure
|
- name: Notify with result failure
|
||||||
uses: peter-evans/create-or-update-comment@c6c9a1a66007646a28c153e2a8580a5bad27bcfa # v3.0.2
|
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
|
||||||
if: ${{ github.event.comment != null && failure() }}
|
if: ${{ github.event.comment != null && failure() }}
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.JF_BOT_TOKEN }}
|
token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
name: Stale Check
|
name: Stale Issue Labeler
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '30 */12 * * *'
|
- cron: '30 1 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
@ -16,37 +16,20 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8
|
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
ascending: true
|
||||||
days-before-stale: 120
|
days-before-stale: 120
|
||||||
days-before-pr-stale: -1
|
days-before-pr-stale: -1
|
||||||
days-before-close: 21
|
days-before-close: 21
|
||||||
days-before-pr-close: -1
|
days-before-pr-close: -1
|
||||||
operations-per-run: 75
|
operations-per-run: 500
|
||||||
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
exempt-issue-labels: regression,security,roadmap,future,feature,enhancement,confirmed
|
||||||
stale-issue-label: stale
|
stale-issue-label: stale
|
||||||
stale-issue-message: |-
|
stale-issue-message: |-
|
||||||
This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs.
|
This issue has gone 120 days without an update and will be closed within 21 days if there is no new activity. To prevent this issue from being closed, please confirm the issue has not already been fixed by providing updated examples or logs.
|
||||||
|
|
||||||
If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact).
|
If you have any questions you can use one of several ways to [contact us](https://jellyfin.org/contact).
|
||||||
close-issue-message: |-
|
close-issue-message: |-
|
||||||
This issue was closed due to inactivity.
|
This issue was closed due to inactivity.
|
||||||
|
|
||||||
prs-conflicts:
|
|
||||||
name: Check PRs with merge conflicts
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ contains(github.repository, 'jellyfin/') }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
|
|
||||||
with:
|
|
||||||
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
|
||||||
operations-per-run: 75
|
|
||||||
# The merge conflict action will remove the label when updated
|
|
||||||
remove-stale-when-updated: false
|
|
||||||
days-before-stale: -1
|
|
||||||
days-before-close: 90
|
|
||||||
days-before-issue-close: -1
|
|
||||||
stale-pr-label: merge conflict
|
|
||||||
close-pr-message: |-
|
|
||||||
This PR has been closed due to having unresolved merge conflicts.
|
|
|
@ -1,4 +1,4 @@
|
||||||
name: Automation
|
name: Project Automation
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -9,19 +9,6 @@ on:
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
|
||||||
name: Labeling
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
|
||||||
steps:
|
|
||||||
- name: Apply label
|
|
||||||
uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0
|
|
||||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
|
||||||
with:
|
|
||||||
dirtyLabel: 'merge conflict'
|
|
||||||
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
|
|
||||||
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
|
||||||
|
|
||||||
project:
|
project:
|
||||||
name: Project board
|
name: Project board
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
|
@ -0,0 +1,23 @@
|
||||||
|
name: Merge Conflict Labeler
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request_target:
|
||||||
|
issue_comment:
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
name: Labeling
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository == 'jellyfin/jellyfin' }}
|
||||||
|
steps:
|
||||||
|
- name: Apply label
|
||||||
|
uses: eps1lon/actions-label-merge-conflict@fd1f295ee7443d13745804bc49fe158e240f6c6e # tag=v2.1.0
|
||||||
|
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request_target'}}
|
||||||
|
with:
|
||||||
|
dirtyLabel: 'merge conflict'
|
||||||
|
commentOnDirty: 'This pull request has merge conflicts. Please resolve the conflicts so the PR can be successfully reviewed and merged.'
|
||||||
|
repoToken: ${{ secrets.JF_BOT_TOKEN }}
|
|
@ -0,0 +1,30 @@
|
||||||
|
name: Stale PR Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '30 */12 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prs-stale-conflicts:
|
||||||
|
name: Check PRs with merge conflicts
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ contains(github.repository, 'jellyfin/') }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
|
||||||
|
with:
|
||||||
|
repo-token: ${{ secrets.JF_BOT_TOKEN }}
|
||||||
|
ascending: true
|
||||||
|
operations-per-run: 150
|
||||||
|
# The merge conflict action will remove the label when updated
|
||||||
|
remove-stale-when-updated: false
|
||||||
|
days-before-stale: -1
|
||||||
|
days-before-close: 90
|
||||||
|
days-before-issue-close: -1
|
||||||
|
stale-pr-label: merge conflict
|
||||||
|
close-pr-message: |-
|
||||||
|
This PR has been closed due to having unresolved merge conflicts.
|
|
@ -33,7 +33,7 @@ jobs:
|
||||||
yq-version: v4.9.8
|
yq-version: v4.9.8
|
||||||
|
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ jobs:
|
||||||
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
NEXT_VERSION: ${{ github.event.inputs.NEXT_VERSION }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- name: Checkout Repository
|
||||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ env.TAG_BRANCH }}
|
ref: ${{ env.TAG_BRANCH }}
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
- [hawken93](https://github.com/hawken93)
|
- [hawken93](https://github.com/hawken93)
|
||||||
- [HelloWorld017](https://github.com/HelloWorld017)
|
- [HelloWorld017](https://github.com/HelloWorld017)
|
||||||
- [ikomhoog](https://github.com/ikomhoog)
|
- [ikomhoog](https://github.com/ikomhoog)
|
||||||
|
- [iwalton3](https://github.com/iwalton3)
|
||||||
- [jftuga](https://github.com/jftuga)
|
- [jftuga](https://github.com/jftuga)
|
||||||
- [jmshrv](https://github.com/jmshrv)
|
- [jmshrv](https://github.com/jmshrv)
|
||||||
- [joern-h](https://github.com/joern-h)
|
- [joern-h](https://github.com/joern-h)
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
- [neilsb](https://github.com/neilsb)
|
- [neilsb](https://github.com/neilsb)
|
||||||
- [nevado](https://github.com/nevado)
|
- [nevado](https://github.com/nevado)
|
||||||
- [Nickbert7](https://github.com/Nickbert7)
|
- [Nickbert7](https://github.com/Nickbert7)
|
||||||
|
- [nicknsy](https://github.com/nicknsy)
|
||||||
- [nvllsvm](https://github.com/nvllsvm)
|
- [nvllsvm](https://github.com/nvllsvm)
|
||||||
- [nyanmisaka](https://github.com/nyanmisaka)
|
- [nyanmisaka](https://github.com/nyanmisaka)
|
||||||
- [OancaAndrei](https://github.com/OancaAndrei)
|
- [OancaAndrei](https://github.com/OancaAndrei)
|
||||||
|
@ -168,6 +170,8 @@
|
||||||
- [TheTyrius](https://github.com/TheTyrius)
|
- [TheTyrius](https://github.com/TheTyrius)
|
||||||
- [tallbl0nde](https://github.com/tallbl0nde)
|
- [tallbl0nde](https://github.com/tallbl0nde)
|
||||||
- [sleepycatcoding](https://github.com/sleepycatcoding)
|
- [sleepycatcoding](https://github.com/sleepycatcoding)
|
||||||
|
- [scampower3](https://github.com/scampower3)
|
||||||
|
- [Chris-Codes-It] (https://github.com/Chris-Codes-It)
|
||||||
- [Pithaya](https://github.com/Pithaya)
|
- [Pithaya](https://github.com/Pithaya)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
@ -239,4 +243,4 @@
|
||||||
- [Jakob Kukla](https://github.com/jakobkukla)
|
- [Jakob Kukla](https://github.com/jakobkukla)
|
||||||
- [Utku Özdemir](https://github.com/utkuozdemir)
|
- [Utku Özdemir](https://github.com/utkuozdemir)
|
||||||
- [JPUC1143](https://github.com/Jpuc1143/)
|
- [JPUC1143](https://github.com/Jpuc1143/)
|
||||||
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
|
- [0x25CBFC4F](https://github.com/0x25CBFC4F)
|
|
@ -2,9 +2,7 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||||
|
|
||||||
<ItemGroup Label="Package Dependencies">
|
<ItemGroup Label="Package Dependencies">
|
||||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.0" />
|
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.0" />
|
||||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
|
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
|
||||||
|
@ -17,23 +15,23 @@
|
||||||
<PackageVersion Include="Diacritics" Version="3.3.18" />
|
<PackageVersion Include="Diacritics" Version="3.3.18" />
|
||||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.9.2" />
|
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="4.0.0" />
|
||||||
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
|
<PackageVersion Include="FsCheck.Xunit" Version="2.16.6" />
|
||||||
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0" />
|
<PackageVersion Include="HarfBuzzSharp.NativeAssets.Linux" Version="7.3.0" />
|
||||||
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.7" />
|
<PackageVersion Include="IDisposableAnalyzers" Version="4.0.4" />
|
||||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageVersion Include="libse" Version="3.6.13" />
|
<PackageVersion Include="libse" Version="3.6.13" />
|
||||||
<PackageVersion Include="LrcParser" Version="2023.524.0" />
|
<PackageVersion Include="LrcParser" Version="2023.524.0" />
|
||||||
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.0" />
|
<PackageVersion Include="MetaBrainz.MusicBrainz" Version="5.0.1" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
<PackageVersion Include="Microsoft.AspNetCore.HttpOverrides" Version="2.2.0" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.Data.Sqlite" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
|
@ -42,14 +40,14 @@
|
||||||
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.11" />
|
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="7.0.13" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Http" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.1" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||||
|
@ -57,14 +55,14 @@
|
||||||
<PackageVersion Include="NEbml" Version="0.11.0" />
|
<PackageVersion Include="NEbml" Version="0.11.0" />
|
||||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageVersion Include="PlaylistsNET" Version="1.4.0" />
|
<PackageVersion Include="PlaylistsNET" Version="1.4.0" />
|
||||||
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.0.1" />
|
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.1.0" />
|
||||||
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.0" />
|
||||||
<PackageVersion Include="prometheus-net" Version="8.0.1" />
|
<PackageVersion Include="prometheus-net" Version="8.1.0" />
|
||||||
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
|
<PackageVersion Include="Serilog.AspNetCore" Version="7.0.0" />
|
||||||
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
<PackageVersion Include="Serilog.Enrichers.Thread" Version="3.1.0" />
|
||||||
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.1" />
|
<PackageVersion Include="Serilog.Settings.Configuration" Version="7.0.1" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
<PackageVersion Include="Serilog.Sinks.Async" Version="1.5.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Console" Version="4.1.0" />
|
<PackageVersion Include="Serilog.Sinks.Console" Version="5.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageVersion Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.0" />
|
<PackageVersion Include="Serilog.Sinks.Graylog" Version="3.1.0" />
|
||||||
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
<PackageVersion Include="SerilogAnalyzer" Version="0.15.0" />
|
||||||
|
@ -72,9 +70,9 @@
|
||||||
<PackageVersion Include="SkiaSharp" Version="2.88.5" />
|
<PackageVersion Include="SkiaSharp" Version="2.88.5" />
|
||||||
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" />
|
<PackageVersion Include="SkiaSharp.HarfBuzz" Version="2.88.5" />
|
||||||
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.5" />
|
||||||
<PackageVersion Include="SkiaSharp.Svg" Version="1.60.0" />
|
|
||||||
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
<PackageVersion Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" />
|
||||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.507" />
|
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.507" />
|
||||||
|
<PackageVersion Include="Svg.Skia" Version="1.0.0.2" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
|
<PackageVersion Include="Swashbuckle.AspNetCore.ReDoc" Version="6.5.0" />
|
||||||
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
<PackageVersion Include="System.Globalization" Version="4.3.0" />
|
||||||
|
@ -86,8 +84,8 @@
|
||||||
<PackageVersion Include="TMDbLib" Version="2.0.0" />
|
<PackageVersion Include="TMDbLib" Version="2.0.0" />
|
||||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||||
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
<PackageVersion Include="Xunit.Priority" Version="1.1.6" />
|
||||||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.1" />
|
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||||
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
|
<PackageVersion Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||||
<PackageVersion Include="xunit" Version="2.5.1" />
|
<PackageVersion Include="xunit" Version="2.6.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -494,7 +494,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
{
|
{
|
||||||
var folder = (Folder)item;
|
var folder = (Folder)item;
|
||||||
|
|
||||||
string[] mediaTypes = Array.Empty<string>();
|
MediaType[] mediaTypes = Array.Empty<MediaType>();
|
||||||
bool? isFolder = null;
|
bool? isFolder = null;
|
||||||
|
|
||||||
switch (search.SearchType)
|
switch (search.SearchType)
|
||||||
|
@ -565,30 +565,18 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
|
|
||||||
if (stubType != StubType.Folder && item is IHasCollectionType collectionFolder)
|
if (stubType != StubType.Folder && item is IHasCollectionType collectionFolder)
|
||||||
{
|
{
|
||||||
var collectionType = collectionFolder.CollectionType;
|
switch (collectionFolder.CollectionType)
|
||||||
if (string.Equals(CollectionType.Music, collectionType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
|
case CollectionType.Music:
|
||||||
}
|
return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
|
case CollectionType.Movies:
|
||||||
if (string.Equals(CollectionType.Movies, collectionType, StringComparison.OrdinalIgnoreCase))
|
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
{
|
case CollectionType.TvShows:
|
||||||
return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
|
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
|
||||||
}
|
case CollectionType.Folders:
|
||||||
|
return GetFolders(user, startIndex, limit);
|
||||||
if (string.Equals(CollectionType.TvShows, collectionType, StringComparison.OrdinalIgnoreCase))
|
case CollectionType.LiveTv:
|
||||||
{
|
return GetLiveTvChannels(user, sort, startIndex, limit);
|
||||||
return GetTvFolders(item, user, stubType, sort, startIndex, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(CollectionType.Folders, collectionType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetFolders(user, startIndex, limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(CollectionType.LiveTv, collectionType, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return GetLiveTvChannels(user, sort, startIndex, limit);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -917,7 +905,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetGenres(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetGenres(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
// Don't sort
|
// Don't sort
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var genresResult = _libraryManager.GetGenres(query);
|
var genresResult = _libraryManager.GetGenres(query);
|
||||||
|
|
||||||
|
@ -933,7 +921,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetMusicGenres(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicGenres(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
// Don't sort
|
// Don't sort
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var genresResult = _libraryManager.GetMusicGenres(query);
|
var genresResult = _libraryManager.GetMusicGenres(query);
|
||||||
|
|
||||||
|
@ -949,7 +937,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetMusicAlbumArtists(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicAlbumArtists(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
// Don't sort
|
// Don't sort
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var artists = _libraryManager.GetAlbumArtists(query);
|
var artists = _libraryManager.GetAlbumArtists(query);
|
||||||
|
|
||||||
|
@ -965,7 +953,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetMusicArtists(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetMusicArtists(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
// Don't sort
|
// Don't sort
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
var artists = _libraryManager.GetArtists(query);
|
var artists = _libraryManager.GetArtists(query);
|
||||||
return ToResult(query.StartIndex, artists);
|
return ToResult(query.StartIndex, artists);
|
||||||
|
@ -980,7 +968,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
private QueryResult<ServerItem> GetFavoriteArtists(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetFavoriteArtists(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
// Don't sort
|
// Don't sort
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
query.AncestorIds = new[] { parent.Id };
|
query.AncestorIds = new[] { parent.Id };
|
||||||
query.IsFavorite = true;
|
query.IsFavorite = true;
|
||||||
var artists = _libraryManager.GetArtists(query);
|
var artists = _libraryManager.GetArtists(query);
|
||||||
|
@ -1011,7 +999,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
|
private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
|
|
||||||
var result = _tvSeriesManager.GetNextUp(
|
var result = _tvSeriesManager.GetNextUp(
|
||||||
new NextUpQuery
|
new NextUpQuery
|
||||||
|
@ -1036,7 +1024,7 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
/// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
|
||||||
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
|
private QueryResult<ServerItem> GetLatest(BaseItem parent, InternalItemsQuery query, BaseItemKind itemType)
|
||||||
{
|
{
|
||||||
query.OrderBy = Array.Empty<(string, SortOrder)>();
|
query.OrderBy = Array.Empty<(ItemSortBy, SortOrder)>();
|
||||||
|
|
||||||
var items = _userViewManager.GetLatestItems(
|
var items = _userViewManager.GetLatestItems(
|
||||||
new LatestItemsQuery
|
new LatestItemsQuery
|
||||||
|
@ -1203,9 +1191,9 @@ namespace Emby.Dlna.ContentDirectory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sort">The <see cref="SortCriteria"/>.</param>
|
/// <param name="sort">The <see cref="SortCriteria"/>.</param>
|
||||||
/// <param name="isPreSorted">True if pre-sorted.</param>
|
/// <param name="isPreSorted">True if pre-sorted.</param>
|
||||||
private static (string SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
|
private static (ItemSortBy SortName, SortOrder SortOrder)[] GetOrderBy(SortCriteria sort, bool isPreSorted)
|
||||||
{
|
{
|
||||||
return isPreSorted ? Array.Empty<(string, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
return isPreSorted ? Array.Empty<(ItemSortBy, SortOrder)>() : new[] { (ItemSortBy.SortName, sort.SortOrder) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -174,13 +174,14 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (item is IHasMediaSources)
|
if (item is IHasMediaSources)
|
||||||
{
|
{
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
switch (item.MediaType)
|
||||||
{
|
{
|
||||||
AddAudioResource(writer, item, deviceId, filter, streamInfo);
|
case MediaType.Audio:
|
||||||
}
|
AddAudioResource(writer, item, deviceId, filter, streamInfo);
|
||||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
break;
|
||||||
{
|
case MediaType.Video:
|
||||||
AddVideoResource(writer, item, deviceId, filter, streamInfo);
|
AddVideoResource(writer, item, deviceId, filter, streamInfo);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -821,15 +822,15 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
writer.WriteString(classType ?? "object.container.storageFolder");
|
writer.WriteString(classType ?? "object.container.storageFolder");
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
else if (item.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
writer.WriteString("object.item.audioItem.musicTrack");
|
writer.WriteString("object.item.audioItem.musicTrack");
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
else if (item.MediaType == MediaType.Photo)
|
||||||
{
|
{
|
||||||
writer.WriteString("object.item.imageItem.photo");
|
writer.WriteString("object.item.imageItem.photo");
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
else if (item.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
if (!_profile.RequiresPlainVideoItems && item is Movie)
|
if (!_profile.RequiresPlainVideoItems && item is Movie)
|
||||||
{
|
{
|
||||||
|
@ -1006,8 +1007,7 @@ namespace Emby.Dlna.Didl
|
||||||
|
|
||||||
if (!_profile.EnableAlbumArtInDidl)
|
if (!_profile.EnableAlbumArtInDidl)
|
||||||
{
|
{
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase)
|
if (item.MediaType == MediaType.Audio || item.MediaType == MediaType.Video)
|
||||||
|| string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
if (!stubType.HasValue)
|
if (!stubType.HasValue)
|
||||||
{
|
{
|
||||||
|
@ -1016,7 +1016,7 @@ namespace Emby.Dlna.Didl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_profile.EnableSingleAlbumArtLimit || string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
if (!_profile.EnableSingleAlbumArtLimit || item.MediaType == MediaType.Photo)
|
||||||
{
|
{
|
||||||
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
AddImageResElement(item, writer, 4096, 4096, "jpg", "JPEG_LRG");
|
||||||
AddImageResElement(item, writer, 1024, 768, "jpg", "JPEG_MED");
|
AddImageResElement(item, writer, 1024, 768, "jpg", "JPEG_MED");
|
||||||
|
|
|
@ -228,7 +228,7 @@ namespace Emby.Dlna
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _fileSystem.GetFilePaths(path)
|
return _fileSystem.GetFilePaths(path)
|
||||||
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
|
.Where(i => Path.GetExtension(i.AsSpan()).Equals(".xml", StringComparison.OrdinalIgnoreCase))
|
||||||
.Select(i => ParseProfileFile(i, type))
|
.Select(i => ParseProfileFile(i, type))
|
||||||
.Where(i => i is not null)
|
.Where(i => i is not null)
|
||||||
.ToList()!; // We just filtered out all the nulls
|
.ToList()!; // We just filtered out all the nulls
|
||||||
|
|
|
@ -26,8 +26,12 @@
|
||||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers -->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="IDisposableAnalyzers">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using Emby.Dlna.ConnectionManager;
|
||||||
|
using Emby.Dlna.ContentDirectory;
|
||||||
|
using Emby.Dlna.Main;
|
||||||
|
using Emby.Dlna.MediaReceiverRegistrar;
|
||||||
|
using Emby.Dlna.Ssdp;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.Extensions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for adding DLNA services.
|
||||||
|
/// </summary>
|
||||||
|
public static class DlnaServiceCollectionExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds DLNA services to the provided <see cref="IServiceCollection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="services">The <see cref="IServiceCollection"/>.</param>
|
||||||
|
/// <param name="applicationHost">The <see cref="IServerApplicationHost"/>.</param>
|
||||||
|
public static void AddDlnaServices(
|
||||||
|
this IServiceCollection services,
|
||||||
|
IServerApplicationHost applicationHost)
|
||||||
|
{
|
||||||
|
services.AddHttpClient(NamedClient.Dlna, c =>
|
||||||
|
{
|
||||||
|
c.DefaultRequestHeaders.UserAgent.ParseAdd(
|
||||||
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}/{1} UPnP/1.0 {2}/{3}",
|
||||||
|
Environment.OSVersion.Platform,
|
||||||
|
Environment.OSVersion,
|
||||||
|
applicationHost.Name,
|
||||||
|
applicationHost.ApplicationVersionString));
|
||||||
|
|
||||||
|
c.DefaultRequestHeaders.Add("CPFN.UPNP.ORG", applicationHost.FriendlyName); // Required for UPnP DeviceArchitecture v2.0
|
||||||
|
c.DefaultRequestHeaders.Add("FriendlyName.DLNA.ORG", applicationHost.FriendlyName); // REVIEW: where does this come from?
|
||||||
|
})
|
||||||
|
.ConfigurePrimaryHttpMessageHandler(_ => new SocketsHttpHandler
|
||||||
|
{
|
||||||
|
AutomaticDecompression = DecompressionMethods.All,
|
||||||
|
RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddSingleton<IDlnaManager, DlnaManager>();
|
||||||
|
services.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
||||||
|
services.AddSingleton<IContentDirectory, ContentDirectoryService>();
|
||||||
|
services.AddSingleton<IConnectionManager, ConnectionManagerService>();
|
||||||
|
services.AddSingleton<IMediaReceiverRegistrar, MediaReceiverRegistrarService>();
|
||||||
|
|
||||||
|
services.AddSingleton<ISsdpCommunicationsServer>(provider => new SsdpCommunicationsServer(
|
||||||
|
provider.GetRequiredService<ISocketFactory>(),
|
||||||
|
provider.GetRequiredService<INetworkManager>(),
|
||||||
|
provider.GetRequiredService<ILogger<SsdpCommunicationsServer>>())
|
||||||
|
{
|
||||||
|
IsShared = true
|
||||||
|
});
|
||||||
|
|
||||||
|
services.AddHostedService<DlnaHost>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,463 +0,0 @@
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Globalization;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Net.Sockets;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Emby.Dlna.PlayTo;
|
|
||||||
using Emby.Dlna.Ssdp;
|
|
||||||
using Jellyfin.Networking.Configuration;
|
|
||||||
using Jellyfin.Networking.Extensions;
|
|
||||||
using MediaBrowser.Common.Configuration;
|
|
||||||
using MediaBrowser.Common.Extensions;
|
|
||||||
using MediaBrowser.Common.Net;
|
|
||||||
using MediaBrowser.Controller;
|
|
||||||
using MediaBrowser.Controller.Configuration;
|
|
||||||
using MediaBrowser.Controller.Dlna;
|
|
||||||
using MediaBrowser.Controller.Drawing;
|
|
||||||
using MediaBrowser.Controller.Library;
|
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
|
||||||
using MediaBrowser.Controller.Plugins;
|
|
||||||
using MediaBrowser.Controller.Session;
|
|
||||||
using MediaBrowser.Controller.TV;
|
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
|
||||||
using MediaBrowser.Model.Net;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Rssdp;
|
|
||||||
using Rssdp.Infrastructure;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Main
|
|
||||||
{
|
|
||||||
public sealed class DlnaEntryPoint : IServerEntryPoint, IRunBeforeStartup
|
|
||||||
{
|
|
||||||
private readonly IServerConfigurationManager _config;
|
|
||||||
private readonly ILogger<DlnaEntryPoint> _logger;
|
|
||||||
private readonly IServerApplicationHost _appHost;
|
|
||||||
private readonly ISessionManager _sessionManager;
|
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
|
||||||
private readonly IUserManager _userManager;
|
|
||||||
private readonly IDlnaManager _dlnaManager;
|
|
||||||
private readonly IImageProcessor _imageProcessor;
|
|
||||||
private readonly IUserDataManager _userDataManager;
|
|
||||||
private readonly ILocalizationManager _localization;
|
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
|
||||||
private readonly IDeviceDiscovery _deviceDiscovery;
|
|
||||||
private readonly ISocketFactory _socketFactory;
|
|
||||||
private readonly INetworkManager _networkManager;
|
|
||||||
private readonly object _syncLock = new object();
|
|
||||||
private readonly bool _disabled;
|
|
||||||
|
|
||||||
private PlayToManager _manager;
|
|
||||||
private SsdpDevicePublisher _publisher;
|
|
||||||
private ISsdpCommunicationsServer _communicationsServer;
|
|
||||||
|
|
||||||
private bool _disposed;
|
|
||||||
|
|
||||||
public DlnaEntryPoint(
|
|
||||||
IServerConfigurationManager config,
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
IServerApplicationHost appHost,
|
|
||||||
ISessionManager sessionManager,
|
|
||||||
IHttpClientFactory httpClientFactory,
|
|
||||||
ILibraryManager libraryManager,
|
|
||||||
IUserManager userManager,
|
|
||||||
IDlnaManager dlnaManager,
|
|
||||||
IImageProcessor imageProcessor,
|
|
||||||
IUserDataManager userDataManager,
|
|
||||||
ILocalizationManager localizationManager,
|
|
||||||
IMediaSourceManager mediaSourceManager,
|
|
||||||
IDeviceDiscovery deviceDiscovery,
|
|
||||||
IMediaEncoder mediaEncoder,
|
|
||||||
ISocketFactory socketFactory,
|
|
||||||
INetworkManager networkManager,
|
|
||||||
IUserViewManager userViewManager,
|
|
||||||
ITVSeriesManager tvSeriesManager)
|
|
||||||
{
|
|
||||||
_config = config;
|
|
||||||
_appHost = appHost;
|
|
||||||
_sessionManager = sessionManager;
|
|
||||||
_httpClientFactory = httpClientFactory;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
_userManager = userManager;
|
|
||||||
_dlnaManager = dlnaManager;
|
|
||||||
_imageProcessor = imageProcessor;
|
|
||||||
_userDataManager = userDataManager;
|
|
||||||
_localization = localizationManager;
|
|
||||||
_mediaSourceManager = mediaSourceManager;
|
|
||||||
_deviceDiscovery = deviceDiscovery;
|
|
||||||
_mediaEncoder = mediaEncoder;
|
|
||||||
_socketFactory = socketFactory;
|
|
||||||
_networkManager = networkManager;
|
|
||||||
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>();
|
|
||||||
|
|
||||||
ContentDirectory = new ContentDirectory.ContentDirectoryService(
|
|
||||||
dlnaManager,
|
|
||||||
userDataManager,
|
|
||||||
imageProcessor,
|
|
||||||
libraryManager,
|
|
||||||
config,
|
|
||||||
userManager,
|
|
||||||
loggerFactory.CreateLogger<ContentDirectory.ContentDirectoryService>(),
|
|
||||||
httpClientFactory,
|
|
||||||
localizationManager,
|
|
||||||
mediaSourceManager,
|
|
||||||
userViewManager,
|
|
||||||
mediaEncoder,
|
|
||||||
tvSeriesManager);
|
|
||||||
|
|
||||||
ConnectionManager = new ConnectionManager.ConnectionManagerService(
|
|
||||||
dlnaManager,
|
|
||||||
config,
|
|
||||||
loggerFactory.CreateLogger<ConnectionManager.ConnectionManagerService>(),
|
|
||||||
httpClientFactory);
|
|
||||||
|
|
||||||
MediaReceiverRegistrar = new MediaReceiverRegistrar.MediaReceiverRegistrarService(
|
|
||||||
loggerFactory.CreateLogger<MediaReceiverRegistrar.MediaReceiverRegistrarService>(),
|
|
||||||
httpClientFactory,
|
|
||||||
config);
|
|
||||||
Current = this;
|
|
||||||
|
|
||||||
var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
|
|
||||||
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
|
|
||||||
|
|
||||||
if (_disabled && _config.GetDlnaConfiguration().EnableServer)
|
|
||||||
{
|
|
||||||
_logger.LogError("The DLNA specification does not support HTTPS.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static DlnaEntryPoint Current { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the dlna server is enabled.
|
|
||||||
/// </summary>
|
|
||||||
public static bool Enabled { get; private set; }
|
|
||||||
|
|
||||||
public IContentDirectory ContentDirectory { get; private set; }
|
|
||||||
|
|
||||||
public IConnectionManager ConnectionManager { get; private set; }
|
|
||||||
|
|
||||||
public IMediaReceiverRegistrar MediaReceiverRegistrar { get; private set; }
|
|
||||||
|
|
||||||
public async Task RunAsync()
|
|
||||||
{
|
|
||||||
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (_disabled)
|
|
||||||
{
|
|
||||||
// No use starting as dlna won't work, as we're running purely on HTTPS.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadComponents();
|
|
||||||
|
|
||||||
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnNamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e)
|
|
||||||
{
|
|
||||||
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
ReloadComponents();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReloadComponents()
|
|
||||||
{
|
|
||||||
var options = _config.GetDlnaConfiguration();
|
|
||||||
Enabled = options.EnableServer;
|
|
||||||
|
|
||||||
StartSsdpHandler();
|
|
||||||
|
|
||||||
if (options.EnableServer)
|
|
||||||
{
|
|
||||||
StartDevicePublisher(options);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisposeDevicePublisher();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.EnablePlayTo)
|
|
||||||
{
|
|
||||||
StartPlayToManager();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DisposePlayToManager();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartSsdpHandler()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_communicationsServer is null)
|
|
||||||
{
|
|
||||||
var enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
|
|
||||||
|
|
||||||
_communicationsServer = new SsdpCommunicationsServer(_socketFactory, _networkManager, _logger, enableMultiSocketBinding)
|
|
||||||
{
|
|
||||||
IsShared = true
|
|
||||||
};
|
|
||||||
|
|
||||||
StartDeviceDiscovery(_communicationsServer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting ssdp handlers");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartDeviceDiscovery(ISsdpCommunicationsServer communicationsServer)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (communicationsServer is not null)
|
|
||||||
{
|
|
||||||
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting device discovery");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposeDeviceDiscovery()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing DeviceDiscovery");
|
|
||||||
((DeviceDiscovery)_deviceDiscovery).Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error stopping device discovery");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartDevicePublisher(Configuration.DlnaOptions options)
|
|
||||||
{
|
|
||||||
if (_publisher is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_publisher = new SsdpDevicePublisher(
|
|
||||||
_communicationsServer,
|
|
||||||
Environment.OSVersion.Platform.ToString(),
|
|
||||||
// Can not use VersionString here since that includes OS and version
|
|
||||||
Environment.OSVersion.Version.ToString(),
|
|
||||||
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
|
||||||
{
|
|
||||||
LogFunction = (msg) => _logger.LogDebug("{Msg}", msg),
|
|
||||||
SupportPnpRootDevice = false
|
|
||||||
};
|
|
||||||
|
|
||||||
RegisterServerEndpoints();
|
|
||||||
|
|
||||||
if (options.BlastAliveMessages)
|
|
||||||
{
|
|
||||||
_publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error registering endpoint");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterServerEndpoints()
|
|
||||||
{
|
|
||||||
var udn = CreateUuid(_appHost.SystemId);
|
|
||||||
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
|
||||||
|
|
||||||
// Only get bind addresses in LAN
|
|
||||||
// IPv6 is currently unsupported
|
|
||||||
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
|
||||||
.Where(x => x.Address is not null)
|
|
||||||
.Where(x => x.AddressFamily != AddressFamily.InterNetworkV6)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (validInterfaces.Count == 0)
|
|
||||||
{
|
|
||||||
// No interfaces returned, fall back to loopback
|
|
||||||
validInterfaces = _networkManager.GetLoopbacks().ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var intf in validInterfaces)
|
|
||||||
{
|
|
||||||
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
|
||||||
|
|
||||||
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address);
|
|
||||||
|
|
||||||
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri);
|
|
||||||
|
|
||||||
var device = new SsdpRootDevice
|
|
||||||
{
|
|
||||||
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
|
|
||||||
Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
|
|
||||||
Address = intf.Address,
|
|
||||||
PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.Prefix),
|
|
||||||
FriendlyName = "Jellyfin",
|
|
||||||
Manufacturer = "Jellyfin",
|
|
||||||
ModelName = "Jellyfin Server",
|
|
||||||
Uuid = udn
|
|
||||||
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
SetProperies(device, fullService);
|
|
||||||
_publisher.AddDevice(device);
|
|
||||||
|
|
||||||
var embeddedDevices = new[]
|
|
||||||
{
|
|
||||||
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
|
||||||
"urn:schemas-upnp-org:service:ConnectionManager:1",
|
|
||||||
// "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var subDevice in embeddedDevices)
|
|
||||||
{
|
|
||||||
var embeddedDevice = new SsdpEmbeddedDevice
|
|
||||||
{
|
|
||||||
FriendlyName = device.FriendlyName,
|
|
||||||
Manufacturer = device.Manufacturer,
|
|
||||||
ModelName = device.ModelName,
|
|
||||||
Uuid = udn
|
|
||||||
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
SetProperies(embeddedDevice, subDevice);
|
|
||||||
device.AddDevice(embeddedDevice);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string CreateUuid(string text)
|
|
||||||
{
|
|
||||||
if (!Guid.TryParse(text, out var guid))
|
|
||||||
{
|
|
||||||
guid = text.GetMD5();
|
|
||||||
}
|
|
||||||
|
|
||||||
return guid.ToString("D", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetProperies(SsdpDevice device, string fullDeviceType)
|
|
||||||
{
|
|
||||||
var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var serviceParts = service.Split(':');
|
|
||||||
|
|
||||||
var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
|
|
||||||
|
|
||||||
device.DeviceTypeNamespace = deviceTypeNamespace;
|
|
||||||
device.DeviceClass = serviceParts[1];
|
|
||||||
device.DeviceType = serviceParts[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartPlayToManager()
|
|
||||||
{
|
|
||||||
lock (_syncLock)
|
|
||||||
{
|
|
||||||
if (_manager is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_manager = new PlayToManager(
|
|
||||||
_logger,
|
|
||||||
_sessionManager,
|
|
||||||
_libraryManager,
|
|
||||||
_userManager,
|
|
||||||
_dlnaManager,
|
|
||||||
_appHost,
|
|
||||||
_imageProcessor,
|
|
||||||
_deviceDiscovery,
|
|
||||||
_httpClientFactory,
|
|
||||||
_userDataManager,
|
|
||||||
_localization,
|
|
||||||
_mediaSourceManager,
|
|
||||||
_mediaEncoder);
|
|
||||||
|
|
||||||
_manager.Start();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error starting PlayTo manager");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisposePlayToManager()
|
|
||||||
{
|
|
||||||
lock (_syncLock)
|
|
||||||
{
|
|
||||||
if (_manager is not null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing PlayToManager");
|
|
||||||
_manager.Dispose();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error disposing PlayTo manager");
|
|
||||||
}
|
|
||||||
|
|
||||||
_manager = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisposeDevicePublisher()
|
|
||||||
{
|
|
||||||
if (_publisher is not null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
|
||||||
_publisher.Dispose();
|
|
||||||
_publisher = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisposeDevicePublisher();
|
|
||||||
DisposePlayToManager();
|
|
||||||
DisposeDeviceDiscovery();
|
|
||||||
|
|
||||||
if (_communicationsServer is not null)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Disposing SsdpCommunicationsServer");
|
|
||||||
_communicationsServer.Dispose();
|
|
||||||
_communicationsServer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContentDirectory = null;
|
|
||||||
ConnectionManager = null;
|
|
||||||
MediaReceiverRegistrar = null;
|
|
||||||
Current = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,389 @@
|
||||||
|
#pragma warning disable CA1031 // Do not catch general exception types.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Emby.Dlna.PlayTo;
|
||||||
|
using Emby.Dlna.Ssdp;
|
||||||
|
using Jellyfin.Networking.Configuration;
|
||||||
|
using Jellyfin.Networking.Extensions;
|
||||||
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Session;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using MediaBrowser.Model.Globalization;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Rssdp;
|
||||||
|
using Rssdp.Infrastructure;
|
||||||
|
|
||||||
|
namespace Emby.Dlna.Main;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="IHostedService"/> that manages a DLNA server.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DlnaHost : IHostedService, IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger<DlnaHost> _logger;
|
||||||
|
private readonly IServerConfigurationManager _config;
|
||||||
|
private readonly IServerApplicationHost _appHost;
|
||||||
|
private readonly ISessionManager _sessionManager;
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
private readonly IUserDataManager _userDataManager;
|
||||||
|
private readonly ILocalizationManager _localization;
|
||||||
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
private readonly IDeviceDiscovery _deviceDiscovery;
|
||||||
|
private readonly ISsdpCommunicationsServer _communicationsServer;
|
||||||
|
private readonly INetworkManager _networkManager;
|
||||||
|
private readonly object _syncLock = new();
|
||||||
|
|
||||||
|
private SsdpDevicePublisher? _publisher;
|
||||||
|
private PlayToManager? _manager;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="DlnaHost"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="config">The <see cref="IServerConfigurationManager"/>.</param>
|
||||||
|
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
|
||||||
|
/// <param name="appHost">The <see cref="IServerApplicationHost"/>.</param>
|
||||||
|
/// <param name="sessionManager">The <see cref="ISessionManager"/>.</param>
|
||||||
|
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/>.</param>
|
||||||
|
/// <param name="libraryManager">The <see cref="ILibraryManager"/>.</param>
|
||||||
|
/// <param name="userManager">The <see cref="IUserManager"/>.</param>
|
||||||
|
/// <param name="dlnaManager">The <see cref="IDlnaManager"/>.</param>
|
||||||
|
/// <param name="imageProcessor">The <see cref="IImageProcessor"/>.</param>
|
||||||
|
/// <param name="userDataManager">The <see cref="IUserDataManager"/>.</param>
|
||||||
|
/// <param name="localizationManager">The <see cref="ILocalizationManager"/>.</param>
|
||||||
|
/// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/>.</param>
|
||||||
|
/// <param name="deviceDiscovery">The <see cref="IDeviceDiscovery"/>.</param>
|
||||||
|
/// <param name="mediaEncoder">The <see cref="IMediaEncoder"/>.</param>
|
||||||
|
/// <param name="communicationsServer">The <see cref="ISsdpCommunicationsServer"/>.</param>
|
||||||
|
/// <param name="networkManager">The <see cref="INetworkManager"/>.</param>
|
||||||
|
public DlnaHost(
|
||||||
|
IServerConfigurationManager config,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
IServerApplicationHost appHost,
|
||||||
|
ISessionManager sessionManager,
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
IUserManager userManager,
|
||||||
|
IDlnaManager dlnaManager,
|
||||||
|
IImageProcessor imageProcessor,
|
||||||
|
IUserDataManager userDataManager,
|
||||||
|
ILocalizationManager localizationManager,
|
||||||
|
IMediaSourceManager mediaSourceManager,
|
||||||
|
IDeviceDiscovery deviceDiscovery,
|
||||||
|
IMediaEncoder mediaEncoder,
|
||||||
|
ISsdpCommunicationsServer communicationsServer,
|
||||||
|
INetworkManager networkManager)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
_appHost = appHost;
|
||||||
|
_sessionManager = sessionManager;
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
_userManager = userManager;
|
||||||
|
_dlnaManager = dlnaManager;
|
||||||
|
_imageProcessor = imageProcessor;
|
||||||
|
_userDataManager = userDataManager;
|
||||||
|
_localization = localizationManager;
|
||||||
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
_deviceDiscovery = deviceDiscovery;
|
||||||
|
_mediaEncoder = mediaEncoder;
|
||||||
|
_communicationsServer = communicationsServer;
|
||||||
|
_networkManager = networkManager;
|
||||||
|
_logger = loggerFactory.CreateLogger<DlnaHost>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public async Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var netConfig = _config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
|
||||||
|
if (_appHost.ListenWithHttps && netConfig.RequireHttps)
|
||||||
|
{
|
||||||
|
if (_config.GetDlnaConfiguration().EnableServer)
|
||||||
|
{
|
||||||
|
_logger.LogError("The DLNA specification does not support HTTPS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// No use starting as dlna won't work, as we're running purely on HTTPS.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
|
||||||
|
ReloadComponents();
|
||||||
|
|
||||||
|
_config.NamedConfigurationUpdated += OnNamedConfigurationUpdated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNamedConfigurationUpdated(object? sender, ConfigurationUpdateEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.Equals(e.Key, "dlna", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReloadComponents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReloadComponents()
|
||||||
|
{
|
||||||
|
var options = _config.GetDlnaConfiguration();
|
||||||
|
StartDeviceDiscovery();
|
||||||
|
|
||||||
|
if (options.EnableServer)
|
||||||
|
{
|
||||||
|
StartDevicePublisher(options);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisposeDevicePublisher();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.EnablePlayTo)
|
||||||
|
{
|
||||||
|
StartPlayToManager();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DisposePlayToManager();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CreateUuid(string text)
|
||||||
|
{
|
||||||
|
if (!Guid.TryParse(text, out var guid))
|
||||||
|
{
|
||||||
|
guid = text.GetMD5();
|
||||||
|
}
|
||||||
|
|
||||||
|
return guid.ToString("D", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetProperties(SsdpDevice device, string fullDeviceType)
|
||||||
|
{
|
||||||
|
var serviceParts = fullDeviceType
|
||||||
|
.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||||
|
.Split(':');
|
||||||
|
|
||||||
|
device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
|
||||||
|
device.DeviceClass = serviceParts[1];
|
||||||
|
device.DeviceType = serviceParts[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDeviceDiscovery()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error starting device discovery");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDevicePublisher(Configuration.DlnaOptions options)
|
||||||
|
{
|
||||||
|
if (_publisher is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_publisher = new SsdpDevicePublisher(
|
||||||
|
_communicationsServer,
|
||||||
|
Environment.OSVersion.Platform.ToString(),
|
||||||
|
// Can not use VersionString here since that includes OS and version
|
||||||
|
Environment.OSVersion.Version.ToString(),
|
||||||
|
_config.GetDlnaConfiguration().SendOnlyMatchedHost)
|
||||||
|
{
|
||||||
|
LogFunction = msg => _logger.LogDebug("{Msg}", msg),
|
||||||
|
SupportPnpRootDevice = false
|
||||||
|
};
|
||||||
|
|
||||||
|
RegisterServerEndpoints();
|
||||||
|
|
||||||
|
if (options.BlastAliveMessages)
|
||||||
|
{
|
||||||
|
_publisher.StartSendingAliveNotifications(TimeSpan.FromSeconds(options.BlastAliveMessageIntervalSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error registering endpoint");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterServerEndpoints()
|
||||||
|
{
|
||||||
|
var udn = CreateUuid(_appHost.SystemId);
|
||||||
|
var descriptorUri = "/dlna/" + udn + "/description.xml";
|
||||||
|
|
||||||
|
// Only get bind addresses in LAN
|
||||||
|
// IPv6 is currently unsupported
|
||||||
|
var validInterfaces = _networkManager.GetInternalBindAddresses()
|
||||||
|
.Where(x => x.AddressFamily != AddressFamily.InterNetworkV6)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (validInterfaces.Count == 0)
|
||||||
|
{
|
||||||
|
// No interfaces returned, fall back to loopback
|
||||||
|
validInterfaces = _networkManager.GetLoopbacks().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var intf in validInterfaces)
|
||||||
|
{
|
||||||
|
var fullService = "urn:schemas-upnp-org:device:MediaServer:1";
|
||||||
|
|
||||||
|
_logger.LogInformation("Registering publisher for {ResourceName} on {DeviceAddress}", fullService, intf.Address);
|
||||||
|
|
||||||
|
var uri = new UriBuilder(_appHost.GetApiUrlForLocalAccess(intf.Address, false) + descriptorUri);
|
||||||
|
|
||||||
|
var device = new SsdpRootDevice
|
||||||
|
{
|
||||||
|
CacheLifetime = TimeSpan.FromSeconds(1800), // How long SSDP clients can cache this info.
|
||||||
|
Location = uri.Uri, // Must point to the URL that serves your devices UPnP description document.
|
||||||
|
Address = intf.Address,
|
||||||
|
PrefixLength = NetworkExtensions.MaskToCidr(intf.Subnet.Prefix),
|
||||||
|
FriendlyName = "Jellyfin",
|
||||||
|
Manufacturer = "Jellyfin",
|
||||||
|
ModelName = "Jellyfin Server",
|
||||||
|
Uuid = udn
|
||||||
|
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
SetProperties(device, fullService);
|
||||||
|
_publisher!.AddDevice(device);
|
||||||
|
|
||||||
|
var embeddedDevices = new[]
|
||||||
|
{
|
||||||
|
"urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||||
|
"urn:schemas-upnp-org:service:ConnectionManager:1",
|
||||||
|
// "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var subDevice in embeddedDevices)
|
||||||
|
{
|
||||||
|
var embeddedDevice = new SsdpEmbeddedDevice
|
||||||
|
{
|
||||||
|
FriendlyName = device.FriendlyName,
|
||||||
|
Manufacturer = device.Manufacturer,
|
||||||
|
ModelName = device.ModelName,
|
||||||
|
Uuid = udn
|
||||||
|
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
|
||||||
|
};
|
||||||
|
|
||||||
|
SetProperties(embeddedDevice, subDevice);
|
||||||
|
device.AddDevice(embeddedDevice);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartPlayToManager()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (_manager is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_manager = new PlayToManager(
|
||||||
|
_logger,
|
||||||
|
_sessionManager,
|
||||||
|
_libraryManager,
|
||||||
|
_userManager,
|
||||||
|
_dlnaManager,
|
||||||
|
_appHost,
|
||||||
|
_imageProcessor,
|
||||||
|
_deviceDiscovery,
|
||||||
|
_httpClientFactory,
|
||||||
|
_userDataManager,
|
||||||
|
_localization,
|
||||||
|
_mediaSourceManager,
|
||||||
|
_mediaEncoder);
|
||||||
|
|
||||||
|
_manager.Start();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error starting PlayTo manager");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposePlayToManager()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (_manager is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Disposing PlayToManager");
|
||||||
|
_manager.Dispose();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error disposing PlayTo manager");
|
||||||
|
}
|
||||||
|
|
||||||
|
_manager = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisposeDevicePublisher()
|
||||||
|
{
|
||||||
|
if (_publisher is not null)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Disposing SsdpDevicePublisher");
|
||||||
|
_publisher.Dispose();
|
||||||
|
_publisher = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Stop()
|
||||||
|
{
|
||||||
|
DisposeDevicePublisher();
|
||||||
|
DisposePlayToManager();
|
||||||
|
}
|
||||||
|
}
|
|
@ -927,14 +927,11 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
var resElement = container.Element(UPnpNamespaces.Res);
|
var resElement = container.Element(UPnpNamespaces.Res);
|
||||||
|
|
||||||
if (resElement is not null)
|
var info = resElement?.Attribute(UPnpNamespaces.ProtocolInfo);
|
||||||
{
|
|
||||||
var info = resElement.Attribute(UPnpNamespaces.ProtocolInfo);
|
|
||||||
|
|
||||||
if (info is not null && !string.IsNullOrWhiteSpace(info.Value))
|
if (info is not null && !string.IsNullOrWhiteSpace(info.Value))
|
||||||
{
|
{
|
||||||
return info.Value.Split(':');
|
return info.Value.Split(':');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new string[4];
|
return new string[4];
|
||||||
|
@ -1139,7 +1136,6 @@ namespace Emby.Dlna.PlayTo
|
||||||
return new Device(deviceProperties, httpClientFactory, logger);
|
return new Device(deviceProperties, httpClientFactory, logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
private static DeviceIcon CreateIcon(XElement element)
|
private static DeviceIcon CreateIcon(XElement element)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(element);
|
ArgumentNullException.ThrowIfNull(element);
|
||||||
|
@ -1252,11 +1248,10 @@ namespace Emby.Dlna.PlayTo
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_timer?.Dispose();
|
_timer?.Dispose();
|
||||||
|
_timer = null;
|
||||||
|
Properties = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
_timer = null;
|
|
||||||
Properties = null!;
|
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,41 +55,42 @@ namespace Emby.Dlna.PlayTo
|
||||||
var client = _httpClientFactory.CreateClient(NamedClient.Dlna);
|
var client = _httpClientFactory.CreateClient(NamedClient.Dlna);
|
||||||
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
using var response = await client.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
await using MemoryStream ms = new MemoryStream();
|
Stream stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
await response.Content.CopyToAsync(ms, cancellationToken).ConfigureAwait(false);
|
await using (stream.ConfigureAwait(false))
|
||||||
ms.Position = 0;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
return await XDocument.LoadAsync(
|
|
||||||
ms,
|
|
||||||
LoadOptions.None,
|
|
||||||
cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (XmlException)
|
|
||||||
{
|
|
||||||
// try correcting the Xml response with common errors
|
|
||||||
ms.Position = 0;
|
|
||||||
using StreamReader sr = new StreamReader(ms);
|
|
||||||
var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// find and replace unescaped ampersands (&)
|
|
||||||
xmlString = EscapeAmpersandRegex().Replace(xmlString, "&");
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// retry reading Xml
|
|
||||||
using var xmlReader = new StringReader(xmlString);
|
|
||||||
return await XDocument.LoadAsync(
|
return await XDocument.LoadAsync(
|
||||||
xmlReader,
|
stream,
|
||||||
LoadOptions.None,
|
LoadOptions.None,
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (XmlException ex)
|
catch (XmlException)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Failed to parse response");
|
// try correcting the Xml response with common errors
|
||||||
_logger.LogDebug("Malformed response: {Content}\n", xmlString);
|
stream.Position = 0;
|
||||||
|
using StreamReader sr = new StreamReader(stream);
|
||||||
|
var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return null;
|
// find and replace unescaped ampersands (&)
|
||||||
|
xmlString = EscapeAmpersandRegex().Replace(xmlString, "&");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// retry reading Xml
|
||||||
|
using var xmlReader = new StringReader(xmlString);
|
||||||
|
return await XDocument.LoadAsync(
|
||||||
|
xmlReader,
|
||||||
|
LoadOptions.None,
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (XmlException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to parse response");
|
||||||
|
_logger.LogDebug("Malformed response: {Content}\n", xmlString);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna.Didl;
|
using Emby.Dlna.Didl;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Data.Events;
|
using Jellyfin.Data.Events;
|
||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
|
@ -577,7 +578,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string? mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
|
private PlaylistItem GetPlaylistItem(BaseItem item, MediaSourceInfo[] mediaSources, DeviceProfile profile, string deviceId, string? mediaSourceId, int? audioStreamIndex, int? subtitleStreamIndex)
|
||||||
{
|
{
|
||||||
if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
if (item.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
return new PlaylistItem
|
return new PlaylistItem
|
||||||
{
|
{
|
||||||
|
@ -597,7 +598,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (item.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
return new PlaylistItem
|
return new PlaylistItem
|
||||||
{
|
{
|
||||||
|
@ -615,7 +616,7 @@ namespace Emby.Dlna.PlayTo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
if (item.MediaType == MediaType.Photo)
|
||||||
{
|
{
|
||||||
return PlaylistItemFactory.Create((Photo)item, profile);
|
return PlaylistItemFactory.Create((Photo)item, profile);
|
||||||
}
|
}
|
||||||
|
@ -683,16 +684,15 @@ namespace Emby.Dlna.PlayTo
|
||||||
|
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
|
_device.PlaybackStart -= OnDevicePlaybackStart;
|
||||||
|
_device.PlaybackProgress -= OnDevicePlaybackProgress;
|
||||||
|
_device.PlaybackStopped -= OnDevicePlaybackStopped;
|
||||||
|
_device.MediaChanged -= OnDeviceMediaChanged;
|
||||||
|
_deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
|
||||||
|
_device.OnDeviceUnavailable = null;
|
||||||
_device.Dispose();
|
_device.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
_device.PlaybackStart -= OnDevicePlaybackStart;
|
|
||||||
_device.PlaybackProgress -= OnDevicePlaybackProgress;
|
|
||||||
_device.PlaybackStopped -= OnDevicePlaybackStopped;
|
|
||||||
_device.MediaChanged -= OnDeviceMediaChanged;
|
|
||||||
_deviceDiscovery.DeviceLeft -= OnDeviceDiscoveryDeviceLeft;
|
|
||||||
_device.OnDeviceUnavailable = null;
|
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,9 @@ namespace Emby.Dlna.PlayTo
|
||||||
private readonly IMediaSourceManager _mediaSourceManager;
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
||||||
|
private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1);
|
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
public PlayToManager(ILogger logger, ISessionManager sessionManager, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, IDeviceDiscovery deviceDiscovery, IHttpClientFactory httpClientFactory, IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder)
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
|
|
||||||
namespace Emby.Dlna.PlayTo
|
namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
|
@ -33,19 +34,19 @@ namespace Emby.Dlna.PlayTo
|
||||||
{
|
{
|
||||||
var classType = UpnpClass ?? string.Empty;
|
var classType = UpnpClass ?? string.Empty;
|
||||||
|
|
||||||
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Audio, StringComparison.Ordinal) != -1)
|
if (classType.Contains("Audio", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Audio;
|
return "Audio";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classType.IndexOf(MediaBrowser.Model.Entities.MediaType.Video, StringComparison.Ordinal) != -1)
|
if (classType.Contains("Video", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Video;
|
return "Video";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (classType.IndexOf("image", StringComparison.Ordinal) != -1)
|
if (classType.Contains("image", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return MediaBrowser.Model.Entities.MediaType.Photo;
|
return "Photo";
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -318,7 +318,7 @@ namespace Emby.Naming.Common
|
||||||
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
new EpisodeExpression(@"[\._ -]()[Ee][Pp]_?([0-9]+)([^\\/]*)$"),
|
||||||
// <!-- foo.E01., foo.e01. -->
|
// <!-- foo.E01., foo.e01. -->
|
||||||
new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"),
|
new EpisodeExpression(@"[^\\/]*?()\.?[Ee]([0-9]+)\.([^\\/]*)$"),
|
||||||
new EpisodeExpression(@"(?<year>[0-9]{4})[._ -](?<month>[0-9]{2})[._ -](?<day>[0-9]{2})", true)
|
new EpisodeExpression("(?<year>[0-9]{4})[._ -](?<month>[0-9]{2})[._ -](?<day>[0-9]{2})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new[]
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
|
@ -328,7 +328,7 @@ namespace Emby.Naming.Common
|
||||||
"yyyy MM dd"
|
"yyyy MM dd"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new EpisodeExpression(@"(?<day>[0-9]{2})[._ -](?<month>[0-9]{2})[._ -](?<year>[0-9]{4})", true)
|
new EpisodeExpression("(?<day>[0-9]{2})[._ -](?<month>[0-9]{2})[._ -](?<year>[0-9]{4})", true)
|
||||||
{
|
{
|
||||||
DateTimeFormats = new[]
|
DateTimeFormats = new[]
|
||||||
{
|
{
|
||||||
|
@ -376,7 +376,7 @@ namespace Emby.Naming.Common
|
||||||
IsNamed = true,
|
IsNamed = true,
|
||||||
SupportsAbsoluteEpisodeNumbers = false
|
SupportsAbsoluteEpisodeNumbers = false
|
||||||
},
|
},
|
||||||
new EpisodeExpression("[\\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\\/]*)$")
|
new EpisodeExpression(@"[\/._ -]p(?:ar)?t[_. -]()([ivx]+|[0-9]+)([._ -][^\/]*)$")
|
||||||
{
|
{
|
||||||
SupportsAbsoluteEpisodeNumbers = true
|
SupportsAbsoluteEpisodeNumbers = true
|
||||||
},
|
},
|
||||||
|
@ -417,7 +417,7 @@ namespace Emby.Naming.Common
|
||||||
},
|
},
|
||||||
|
|
||||||
// "1-12 episode title"
|
// "1-12 episode title"
|
||||||
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
new EpisodeExpression("([0-9]+)-([0-9]+)"),
|
||||||
|
|
||||||
// "01 - blah.avi", "01-blah.avi"
|
// "01 - blah.avi", "01-blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
|
||||||
|
@ -712,7 +712,7 @@ namespace Emby.Naming.Common
|
||||||
// Chapter is often beginning of filename
|
// Chapter is often beginning of filename
|
||||||
"^(?<chapter>[0-9]+)",
|
"^(?<chapter>[0-9]+)",
|
||||||
// Part if often ending of filename
|
// Part if often ending of filename
|
||||||
@"(?<!ch(?:apter) )(?<part>[0-9]+)$",
|
"(?<!ch(?:apter) )(?<part>[0-9]+)$",
|
||||||
// Sometimes named as 0001_005 (chapter_part)
|
// Sometimes named as 0001_005 (chapter_part)
|
||||||
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
||||||
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
||||||
|
|
|
@ -45,8 +45,12 @@
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers -->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="IDisposableAnalyzers">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
|
|
@ -43,7 +43,7 @@ namespace Emby.Naming.ExternalFiles
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
if (!(_type == DlnaProfileType.Subtitle && _namingOptions.SubtitleFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
&& !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)))
|
&& !(_type == DlnaProfileType.Audio && _namingOptions.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)))
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,19 +26,18 @@ namespace Emby.Naming.Video
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path.AsSpan());
|
||||||
|
|
||||||
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
if (!options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
path = Path.GetFileNameWithoutExtension(path);
|
var token = Path.GetExtension(Path.GetFileNameWithoutExtension(path.AsSpan())).TrimStart('.');
|
||||||
var token = Path.GetExtension(path).TrimStart('.');
|
|
||||||
|
|
||||||
foreach (var rule in options.StubTypes)
|
foreach (var rule in options.StubTypes)
|
||||||
{
|
{
|
||||||
if (string.Equals(rule.Token, token, StringComparison.OrdinalIgnoreCase))
|
if (token.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
stubType = rule.StubType;
|
stubType = rule.StubType;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -24,14 +24,18 @@
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers -->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="IDisposableAnalyzers">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
|
||||||
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
<PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
|
||||||
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ namespace Emby.Photos
|
||||||
item.SetImagePath(ImageType.Primary, item.Path);
|
item.SetImagePath(ImageType.Primary, item.Path);
|
||||||
|
|
||||||
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
// Examples: https://github.com/mono/taglib-sharp/blob/a5f6949a53d09ce63ee7495580d6802921a21f14/tests/fixtures/TagLib.Tests.Images/NullOrientationTest.cs
|
||||||
if (_includeExtensions.Contains(Path.GetExtension(item.Path), StringComparison.OrdinalIgnoreCase))
|
if (_includeExtensions.Contains(Path.GetExtension(item.Path.AsSpan()), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,8 +10,6 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class BaseApplicationPaths : IApplicationPaths
|
public abstract class BaseApplicationPaths : IApplicationPaths
|
||||||
{
|
{
|
||||||
private string _dataPath;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
/// Initializes a new instance of the <see cref="BaseApplicationPaths"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -33,7 +31,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
CachePath = cacheDirectoryPath;
|
CachePath = cacheDirectoryPath;
|
||||||
WebPath = webDirectoryPath;
|
WebPath = webDirectoryPath;
|
||||||
|
|
||||||
_dataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
|
DataPath = Directory.CreateDirectory(Path.Combine(ProgramDataPath, "data")).FullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -55,7 +53,7 @@ namespace Emby.Server.Implementations.AppBase
|
||||||
/// Gets the folder path to the data directory.
|
/// Gets the folder path to the data directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The data directory.</value>
|
/// <value>The data directory.</value>
|
||||||
public string DataPath => _dataPath;
|
public string DataPath { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string VirtualDataPath => "%AppDataPath%";
|
public string VirtualDataPath => "%AppDataPath%";
|
||||||
|
|
|
@ -13,9 +13,7 @@ using System.Net;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Dlna;
|
|
||||||
using Emby.Dlna.Main;
|
using Emby.Dlna.Main;
|
||||||
using Emby.Dlna.Ssdp;
|
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Photos;
|
using Emby.Photos;
|
||||||
using Emby.Server.Implementations.Channels;
|
using Emby.Server.Implementations.Channels;
|
||||||
|
@ -58,7 +56,6 @@ using MediaBrowser.Controller.Chapters;
|
||||||
using MediaBrowser.Controller.ClientEvent;
|
using MediaBrowser.Controller.ClientEvent;
|
||||||
using MediaBrowser.Controller.Collections;
|
using MediaBrowser.Controller.Collections;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dlna;
|
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -82,7 +79,6 @@ using MediaBrowser.LocalMetadata.Savers;
|
||||||
using MediaBrowser.MediaEncoding.BdInfo;
|
using MediaBrowser.MediaEncoding.BdInfo;
|
||||||
using MediaBrowser.MediaEncoding.Subtitles;
|
using MediaBrowser.MediaEncoding.Subtitles;
|
||||||
using MediaBrowser.Model.Cryptography;
|
using MediaBrowser.Model.Cryptography;
|
||||||
using MediaBrowser.Model.Dlna;
|
|
||||||
using MediaBrowser.Model.Globalization;
|
using MediaBrowser.Model.Globalization;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
@ -101,7 +97,6 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Prometheus.DotNetRuntime;
|
using Prometheus.DotNetRuntime;
|
||||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||||
|
@ -133,7 +128,7 @@ namespace Emby.Server.Implementations
|
||||||
/// <value>All concrete types.</value>
|
/// <value>All concrete types.</value>
|
||||||
private Type[] _allConcreteTypes;
|
private Type[] _allConcreteTypes;
|
||||||
|
|
||||||
private bool _disposed = false;
|
private bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost"/> class.
|
||||||
|
@ -184,26 +179,16 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
public bool CoreStartupHasCompleted { get; private set; }
|
public bool CoreStartupHasCompleted { get; private set; }
|
||||||
|
|
||||||
public virtual bool CanLaunchWebBrowser => Environment.UserInteractive
|
|
||||||
&& !_startupOptions.IsService
|
|
||||||
&& (OperatingSystem.IsWindows() || OperatingSystem.IsMacOS());
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
/// Gets the <see cref="INetworkManager"/> singleton instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public INetworkManager NetManager { get; private set; }
|
public INetworkManager NetManager { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Gets a value indicating whether this instance has changes that require the entire application to restart.
|
|
||||||
/// </summary>
|
|
||||||
/// <value><c>true</c> if this instance has pending application restart; otherwise, <c>false</c>.</value>
|
|
||||||
public bool HasPendingRestart { get; private set; }
|
public bool HasPendingRestart { get; private set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsShuttingDown { get; private set; }
|
public bool ShouldRestart { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool ShouldRestart { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the logger.
|
/// Gets the logger.
|
||||||
|
@ -461,7 +446,7 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
ConfigurationManager.AddParts(GetExports<IConfigurationFactory>());
|
||||||
|
|
||||||
NetManager = new NetworkManager(ConfigurationManager, LoggerFactory.CreateLogger<NetworkManager>());
|
NetManager = new NetworkManager(ConfigurationManager, _startupConfig, LoggerFactory.CreateLogger<NetworkManager>());
|
||||||
|
|
||||||
// Initialize runtime stat collection
|
// Initialize runtime stat collection
|
||||||
if (ConfigurationManager.Configuration.EnableMetrics)
|
if (ConfigurationManager.Configuration.EnableMetrics)
|
||||||
|
@ -507,6 +492,8 @@ namespace Emby.Server.Implementations
|
||||||
serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>();
|
serviceCollection.AddSingleton<IFileSystem, ManagedFileSystem>();
|
||||||
serviceCollection.AddSingleton<IShortcutHandler, MbLinkShortcutHandler>();
|
serviceCollection.AddSingleton<IShortcutHandler, MbLinkShortcutHandler>();
|
||||||
|
|
||||||
|
serviceCollection.AddScoped<ISystemManager, SystemManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<TmdbClientManager>();
|
serviceCollection.AddSingleton<TmdbClientManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton(NetManager);
|
serviceCollection.AddSingleton(NetManager);
|
||||||
|
@ -572,8 +559,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
serviceCollection.AddSingleton<ISessionManager, SessionManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDlnaManager, DlnaManager>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
serviceCollection.AddSingleton<ICollectionManager, CollectionManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
serviceCollection.AddSingleton<IPlaylistManager, PlaylistManager>();
|
||||||
|
@ -585,8 +570,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
serviceCollection.AddSingleton<IUserViewManager, UserViewManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IDeviceDiscovery, DeviceDiscovery>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
serviceCollection.AddSingleton<IChapterManager, ChapterManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
serviceCollection.AddSingleton<IEncodingManager, MediaEncoder.EncodingManager>();
|
||||||
|
@ -850,24 +833,6 @@ namespace Emby.Server.Implementations
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Restart()
|
|
||||||
{
|
|
||||||
ShouldRestart = true;
|
|
||||||
Shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void Shutdown()
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
|
||||||
IsShuttingDown = true;
|
|
||||||
Resolve<IHostApplicationLifetime>().StopApplication();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the composable part assemblies.
|
/// Gets the composable part assemblies.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -901,7 +866,7 @@ namespace Emby.Server.Implementations
|
||||||
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
|
yield return typeof(MediaBrowser.MediaEncoding.Encoder.MediaEncoder).Assembly;
|
||||||
|
|
||||||
// Dlna
|
// Dlna
|
||||||
yield return typeof(DlnaEntryPoint).Assembly;
|
yield return typeof(DlnaHost).Assembly;
|
||||||
|
|
||||||
// Local metadata
|
// Local metadata
|
||||||
yield return typeof(BoxSetXmlSaver).Assembly;
|
yield return typeof(BoxSetXmlSaver).Assembly;
|
||||||
|
@ -923,49 +888,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
|
protected abstract IEnumerable<Assembly> GetAssembliesWithPartsInternal();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the system status.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="request">Where this request originated.</param>
|
|
||||||
/// <returns>SystemInfo.</returns>
|
|
||||||
public SystemInfo GetSystemInfo(HttpRequest request)
|
|
||||||
{
|
|
||||||
return new SystemInfo
|
|
||||||
{
|
|
||||||
HasPendingRestart = HasPendingRestart,
|
|
||||||
IsShuttingDown = IsShuttingDown,
|
|
||||||
Version = ApplicationVersionString,
|
|
||||||
WebSocketPortNumber = HttpPort,
|
|
||||||
CompletedInstallations = Resolve<IInstallationManager>().CompletedInstallations.ToArray(),
|
|
||||||
Id = SystemId,
|
|
||||||
ProgramDataPath = ApplicationPaths.ProgramDataPath,
|
|
||||||
WebPath = ApplicationPaths.WebPath,
|
|
||||||
LogPath = ApplicationPaths.LogDirectoryPath,
|
|
||||||
ItemsByNamePath = ApplicationPaths.InternalMetadataPath,
|
|
||||||
InternalMetadataPath = ApplicationPaths.InternalMetadataPath,
|
|
||||||
CachePath = ApplicationPaths.CachePath,
|
|
||||||
CanLaunchWebBrowser = CanLaunchWebBrowser,
|
|
||||||
TranscodingTempPath = ConfigurationManager.GetTranscodePath(),
|
|
||||||
ServerName = FriendlyName,
|
|
||||||
LocalAddress = GetSmartApiUrl(request),
|
|
||||||
SupportsLibraryMonitor = true,
|
|
||||||
PackageName = _startupOptions.PackageName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicSystemInfo GetPublicSystemInfo(HttpRequest request)
|
|
||||||
{
|
|
||||||
return new PublicSystemInfo
|
|
||||||
{
|
|
||||||
Version = ApplicationVersionString,
|
|
||||||
ProductName = ApplicationProductName,
|
|
||||||
Id = SystemId,
|
|
||||||
ServerName = FriendlyName,
|
|
||||||
LocalAddress = GetSmartApiUrl(request),
|
|
||||||
StartupWizardCompleted = ConfigurationManager.CommonConfiguration.IsStartupWizardCompleted
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetSmartApiUrl(IPAddress remoteAddr)
|
public string GetSmartApiUrl(IPAddress remoteAddr)
|
||||||
{
|
{
|
||||||
|
@ -983,7 +905,7 @@ namespace Emby.Server.Implementations
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string GetSmartApiUrl(HttpRequest request)
|
public string GetSmartApiUrl(HttpRequest request)
|
||||||
{
|
{
|
||||||
// Return the host in the HTTP request as the API url
|
// Return the host in the HTTP request as the API URL if not configured otherwise
|
||||||
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
if (ConfigurationManager.GetNetworkConfiguration().EnablePublishedServerUriByRequest)
|
||||||
{
|
{
|
||||||
int? requestPort = request.Host.Port;
|
int? requestPort = request.Host.Port;
|
||||||
|
@ -1018,7 +940,7 @@ namespace Emby.Server.Implementations
|
||||||
public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
|
public string GetApiUrlForLocalAccess(IPAddress ipAddress = null, bool allowHttps = true)
|
||||||
{
|
{
|
||||||
// With an empty source, the port will be null
|
// With an empty source, the port will be null
|
||||||
var smart = NetManager.GetBindAddress(ipAddress, out _, true);
|
var smart = NetManager.GetBindAddress(ipAddress, out _, false);
|
||||||
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
var scheme = !allowHttps ? Uri.UriSchemeHttp : null;
|
||||||
int? port = !allowHttps ? HttpPort : null;
|
int? port = !allowHttps ? HttpPort : null;
|
||||||
return GetLocalApiUrl(smart, scheme, port);
|
return GetLocalApiUrl(smart, scheme, port);
|
||||||
|
|
|
@ -371,8 +371,11 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||||
|
|
||||||
await using FileStream createStream = File.Create(path);
|
FileStream createStream = File.Create(path);
|
||||||
await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
|
await using (createStream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await JsonSerializer.SerializeAsync(createStream, mediaSources, _jsonOptions).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1156,7 +1159,7 @@ namespace Emby.Server.Implementations.Channels
|
||||||
|
|
||||||
if (info.People is not null && info.People.Count > 0)
|
if (info.People is not null && info.People.Count > 0)
|
||||||
{
|
{
|
||||||
_libraryManager.UpdatePeople(item, info.People);
|
await _libraryManager.UpdatePeopleAsync(item, info.People, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (forceUpdate)
|
else if (forceUpdate)
|
||||||
|
|
|
@ -722,7 +722,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
saveItemStatement.TryBind("@IsLocked", item.IsLocked);
|
saveItemStatement.TryBind("@IsLocked", item.IsLocked);
|
||||||
saveItemStatement.TryBind("@Name", item.Name);
|
saveItemStatement.TryBind("@Name", item.Name);
|
||||||
saveItemStatement.TryBind("@OfficialRating", item.OfficialRating);
|
saveItemStatement.TryBind("@OfficialRating", item.OfficialRating);
|
||||||
saveItemStatement.TryBind("@MediaType", item.MediaType);
|
saveItemStatement.TryBind("@MediaType", item.MediaType.ToString());
|
||||||
saveItemStatement.TryBind("@Overview", item.Overview);
|
saveItemStatement.TryBind("@Overview", item.Overview);
|
||||||
saveItemStatement.TryBind("@ParentIndexNumber", item.ParentIndexNumber);
|
saveItemStatement.TryBind("@ParentIndexNumber", item.ParentIndexNumber);
|
||||||
saveItemStatement.TryBind("@PremiereDate", item.PremiereDate);
|
saveItemStatement.TryBind("@PremiereDate", item.PremiereDate);
|
||||||
|
@ -2042,7 +2042,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.OrderBy), StringComparer.OrdinalIgnoreCase);
|
var sortingFields = new HashSet<ItemSortBy>(query.OrderBy.Select(i => i.OrderBy));
|
||||||
|
|
||||||
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|
||||||
|| sortingFields.Contains(ItemSortBy.IsPlayed)
|
|| sortingFields.Contains(ItemSortBy.IsPlayed)
|
||||||
|
@ -2832,20 +2832,20 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
if (hasSimilar || hasSearch)
|
if (hasSimilar || hasSearch)
|
||||||
{
|
{
|
||||||
List<(string, SortOrder)> prepend = new List<(string, SortOrder)>(4);
|
List<(ItemSortBy, SortOrder)> prepend = new List<(ItemSortBy, SortOrder)>(4);
|
||||||
if (hasSearch)
|
if (hasSearch)
|
||||||
{
|
{
|
||||||
prepend.Add(("SearchScore", SortOrder.Descending));
|
prepend.Add((ItemSortBy.SearchScore, SortOrder.Descending));
|
||||||
prepend.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
prepend.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSimilar)
|
if (hasSimilar)
|
||||||
{
|
{
|
||||||
prepend.Add(("SimilarityScore", SortOrder.Descending));
|
prepend.Add((ItemSortBy.SimilarityScore, SortOrder.Descending));
|
||||||
prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
|
prepend.Add((ItemSortBy.Random, SortOrder.Ascending));
|
||||||
}
|
}
|
||||||
|
|
||||||
var arr = new (string, SortOrder)[prepend.Count + orderBy.Count];
|
var arr = new (ItemSortBy, SortOrder)[prepend.Count + orderBy.Count];
|
||||||
prepend.CopyTo(arr, 0);
|
prepend.CopyTo(arr, 0);
|
||||||
orderBy.CopyTo(arr, prepend.Count);
|
orderBy.CopyTo(arr, prepend.Count);
|
||||||
orderBy = query.OrderBy = arr;
|
orderBy = query.OrderBy = arr;
|
||||||
|
@ -2863,166 +2863,43 @@ namespace Emby.Server.Implementations.Data
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string MapOrderByField(string name, InternalItemsQuery query)
|
private string MapOrderByField(ItemSortBy sortBy, InternalItemsQuery query)
|
||||||
{
|
{
|
||||||
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
|
return sortBy switch
|
||||||
{
|
{
|
||||||
// TODO
|
ItemSortBy.AirTime => "SortName", // TODO
|
||||||
return "SortName";
|
ItemSortBy.Runtime => "RuntimeTicks",
|
||||||
}
|
ItemSortBy.Random => "RANDOM()",
|
||||||
|
ItemSortBy.DatePlayed when query.GroupBySeriesPresentationUniqueKey => "MAX(LastPlayedDate)",
|
||||||
if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
|
ItemSortBy.DatePlayed => "LastPlayedDate",
|
||||||
{
|
ItemSortBy.PlayCount => "PlayCount",
|
||||||
return "RuntimeTicks";
|
ItemSortBy.IsFavoriteOrLiked => "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )",
|
||||||
}
|
ItemSortBy.IsFolder => "IsFolder",
|
||||||
|
ItemSortBy.IsPlayed => "played",
|
||||||
if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
|
ItemSortBy.IsUnplayed => "played",
|
||||||
{
|
ItemSortBy.DateLastContentAdded => "DateLastMediaAdded",
|
||||||
return "RANDOM()";
|
ItemSortBy.Artist => "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)",
|
||||||
}
|
ItemSortBy.AlbumArtist => "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)",
|
||||||
|
ItemSortBy.OfficialRating => "InheritedParentalRatingValue",
|
||||||
if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
|
ItemSortBy.Studio => "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)",
|
||||||
{
|
ItemSortBy.SeriesDatePlayed => "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)",
|
||||||
if (query.GroupBySeriesPresentationUniqueKey)
|
ItemSortBy.SeriesSortName => "SeriesName",
|
||||||
{
|
ItemSortBy.AiredEpisodeOrder => "AiredEpisodeOrder",
|
||||||
return "MAX(LastPlayedDate)";
|
ItemSortBy.Album => "Album",
|
||||||
}
|
ItemSortBy.DateCreated => "DateCreated",
|
||||||
|
ItemSortBy.PremiereDate => "PremiereDate",
|
||||||
return "LastPlayedDate";
|
ItemSortBy.StartDate => "StartDate",
|
||||||
}
|
ItemSortBy.Name => "Name",
|
||||||
|
ItemSortBy.CommunityRating => "CommunityRating",
|
||||||
if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
|
ItemSortBy.ProductionYear => "ProductionYear",
|
||||||
{
|
ItemSortBy.CriticRating => "CriticRating",
|
||||||
return ItemSortBy.PlayCount;
|
ItemSortBy.VideoBitRate => "VideoBitRate",
|
||||||
}
|
ItemSortBy.ParentIndexNumber => "ParentIndexNumber",
|
||||||
|
ItemSortBy.IndexNumber => "IndexNumber",
|
||||||
if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
|
ItemSortBy.SimilarityScore => "SimilarityScore",
|
||||||
{
|
ItemSortBy.SearchScore => "SearchScore",
|
||||||
return "(Select Case When IsFavorite is null Then 0 Else IsFavorite End )";
|
_ => "SortName"
|
||||||
}
|
};
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.IsFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "played";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "played";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "DateLastMediaAdded";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=0 LIMIT 1)";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=1 LIMIT 1)";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "InheritedParentalRatingValue";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "(select CleanValue from ItemValues where ItemId=Guid and Type=3 LIMIT 1)";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return "SeriesName";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.AiredEpisodeOrder, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.AiredEpisodeOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Album, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.Album;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.DateCreated, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.DateCreated;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.PremiereDate, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.PremiereDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.StartDate, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.StartDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.Name, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.CommunityRating, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.CommunityRating;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.ProductionYear, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.ProductionYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.CriticRating, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.CriticRating;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.VideoBitRate, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.VideoBitRate;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.ParentIndexNumber, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.ParentIndexNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.IndexNumber, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.IndexNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.SimilarityScore, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.SimilarityScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(name, ItemSortBy.SearchScore, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return ItemSortBy.SearchScore;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unknown SortBy, just sort by the SortName.
|
|
||||||
return ItemSortBy.SortName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Guid> GetItemIdsList(InternalItemsQuery query)
|
public List<Guid> GetItemIdsList(InternalItemsQuery query)
|
||||||
|
@ -3109,11 +2986,6 @@ namespace Emby.Server.Implementations.Data
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidMediaType(string value)
|
|
||||||
{
|
|
||||||
return IsAlphaNumeric(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsValidPersonType(string value)
|
private bool IsValidPersonType(string value)
|
||||||
{
|
{
|
||||||
return IsAlphaNumeric(value);
|
return IsAlphaNumeric(value);
|
||||||
|
@ -3540,10 +3412,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
.Append(paramName)
|
.Append(paramName)
|
||||||
.Append("))) OR ");
|
.Append("))) OR ");
|
||||||
|
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, query.PersonIds[i]);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, query.PersonIds[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clauseBuilder.Length -= Or.Length;
|
clauseBuilder.Length -= Or.Length;
|
||||||
|
@ -4124,15 +3993,14 @@ namespace Emby.Server.Implementations.Data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var queryMediaTypes = query.MediaTypes.Where(IsValidMediaType).ToArray();
|
if (query.MediaTypes.Length == 1)
|
||||||
if (queryMediaTypes.Length == 1)
|
|
||||||
{
|
{
|
||||||
whereClauses.Add("MediaType=@MediaTypes");
|
whereClauses.Add("MediaType=@MediaTypes");
|
||||||
statement?.TryBind("@MediaTypes", queryMediaTypes[0]);
|
statement?.TryBind("@MediaTypes", query.MediaTypes[0].ToString());
|
||||||
}
|
}
|
||||||
else if (queryMediaTypes.Length > 1)
|
else if (query.MediaTypes.Length > 1)
|
||||||
{
|
{
|
||||||
var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'"));
|
var val = string.Join(',', query.MediaTypes.Select(i => $"'{i}'"));
|
||||||
whereClauses.Add("MediaType in (" + val + ")");
|
whereClauses.Add("MediaType in (" + val + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4382,7 +4250,7 @@ namespace Emby.Server.Implementations.Data
|
||||||
|
|
||||||
foreach (var videoType in query.VideoTypes)
|
foreach (var videoType in query.VideoTypes)
|
||||||
{
|
{
|
||||||
videoTypes.Add("data like '%\"VideoType\":\"" + videoType.ToString() + "\"%'");
|
videoTypes.Add("data like '%\"VideoType\":\"" + videoType + "\"%'");
|
||||||
}
|
}
|
||||||
|
|
||||||
whereClauses.Add("(" + string.Join(" OR ", videoTypes) + ")");
|
whereClauses.Add("(" + string.Join(" OR ", videoTypes) + ")");
|
||||||
|
|
|
@ -22,6 +22,7 @@ using MediaBrowser.Controller.Lyrics;
|
||||||
using MediaBrowser.Controller.Persistence;
|
using MediaBrowser.Controller.Persistence;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
using MediaBrowser.Controller.Trickplay;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
|
@ -52,6 +53,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
|
private readonly Lazy<ILiveTvManager> _livetvManagerFactory;
|
||||||
|
|
||||||
private readonly ILyricManager _lyricManager;
|
private readonly ILyricManager _lyricManager;
|
||||||
|
private readonly ITrickplayManager _trickplayManager;
|
||||||
|
|
||||||
public DtoService(
|
public DtoService(
|
||||||
ILogger<DtoService> logger,
|
ILogger<DtoService> logger,
|
||||||
|
@ -63,7 +65,8 @@ namespace Emby.Server.Implementations.Dto
|
||||||
IApplicationHost appHost,
|
IApplicationHost appHost,
|
||||||
IMediaSourceManager mediaSourceManager,
|
IMediaSourceManager mediaSourceManager,
|
||||||
Lazy<ILiveTvManager> livetvManagerFactory,
|
Lazy<ILiveTvManager> livetvManagerFactory,
|
||||||
ILyricManager lyricManager)
|
ILyricManager lyricManager,
|
||||||
|
ITrickplayManager trickplayManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
@ -75,6 +78,7 @@ namespace Emby.Server.Implementations.Dto
|
||||||
_mediaSourceManager = mediaSourceManager;
|
_mediaSourceManager = mediaSourceManager;
|
||||||
_livetvManagerFactory = livetvManagerFactory;
|
_livetvManagerFactory = livetvManagerFactory;
|
||||||
_lyricManager = lyricManager;
|
_lyricManager = lyricManager;
|
||||||
|
_trickplayManager = trickplayManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
|
private ILiveTvManager LivetvManager => _livetvManagerFactory.Value;
|
||||||
|
@ -1059,6 +1063,11 @@ namespace Emby.Server.Implementations.Dto
|
||||||
dto.Chapters = _itemRepo.GetChapters(item);
|
dto.Chapters = _itemRepo.GetChapters(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.ContainsField(ItemFields.Trickplay))
|
||||||
|
{
|
||||||
|
dto.Trickplay = _trickplayManager.GetTrickplayManifest(item).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
if (video.ExtraType.HasValue)
|
if (video.ExtraType.HasValue)
|
||||||
{
|
{
|
||||||
dto.ExtraType = video.ExtraType.Value.ToString();
|
dto.ExtraType = video.ExtraType.Value.ToString();
|
||||||
|
|
|
@ -43,16 +43,19 @@
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<!-- https://github.com/microsoft/ApplicationInsights-dotnet/issues/2047 -->
|
|
||||||
<NoWarn>AD0001</NoWarn>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
<CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- Code Analyzers-->
|
<!-- Code Analyzers -->
|
||||||
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<!-- TODO: Add IDisposableAnalyzers -->
|
||||||
|
<!-- <PackageReference Include="IDisposableAnalyzers">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference> -->
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
|
|
@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Emby.Server.Implementations.EntryPoints
|
namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class UdpServerEntryPoint.
|
/// Class responsible for registering all UDP broadcast endpoints and their handlers.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class UdpServerEntryPoint : IServerEntryPoint
|
public sealed class UdpServerEntryPoint : IServerEntryPoint
|
||||||
{
|
{
|
||||||
|
@ -35,14 +35,13 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
private readonly IConfiguration _config;
|
private readonly IConfiguration _config;
|
||||||
private readonly IConfigurationManager _configurationManager;
|
private readonly IConfigurationManager _configurationManager;
|
||||||
private readonly INetworkManager _networkManager;
|
private readonly INetworkManager _networkManager;
|
||||||
private readonly bool _enableMultiSocketBinding;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The UDP server.
|
/// The UDP server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private List<UdpServer> _udpServers;
|
private readonly List<UdpServer> _udpServers;
|
||||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||||
private bool _disposed = false;
|
private bool _disposed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
|
/// Initializes a new instance of the <see cref="UdpServerEntryPoint" /> class.
|
||||||
|
@ -65,7 +64,6 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
_networkManager = networkManager;
|
_networkManager = networkManager;
|
||||||
_udpServers = new List<UdpServer>();
|
_udpServers = new List<UdpServer>();
|
||||||
_enableMultiSocketBinding = OperatingSystem.IsWindows() || OperatingSystem.IsLinux();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -80,14 +78,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_enableMultiSocketBinding)
|
// Linux needs to bind to the broadcast addresses to get broadcast traffic
|
||||||
|
// Windows receives broadcast fine when binding to just the interface, it is unable to bind to broadcast addresses
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
{
|
{
|
||||||
// Add global broadcast socket
|
// Add global broadcast listener
|
||||||
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
|
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Broadcast, PortNumber);
|
||||||
server.Start(_cancellationTokenSource.Token);
|
server.Start(_cancellationTokenSource.Token);
|
||||||
_udpServers.Add(server);
|
_udpServers.Add(server);
|
||||||
|
|
||||||
// Add bind address specific broadcast sockets
|
// Add bind address specific broadcast listeners
|
||||||
// IPv6 is currently unsupported
|
// IPv6 is currently unsupported
|
||||||
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||||
foreach (var intf in validInterfaces)
|
foreach (var intf in validInterfaces)
|
||||||
|
@ -102,9 +102,18 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var server = new UdpServer(_logger, _appHost, _config, IPAddress.Any, PortNumber);
|
// Add bind address specific broadcast listeners
|
||||||
server.Start(_cancellationTokenSource.Token);
|
// IPv6 is currently unsupported
|
||||||
_udpServers.Add(server);
|
var validInterfaces = _networkManager.GetInternalBindAddresses().Where(i => i.AddressFamily == AddressFamily.InterNetwork);
|
||||||
|
foreach (var intf in validInterfaces)
|
||||||
|
{
|
||||||
|
var intfAddress = intf.Address;
|
||||||
|
_logger.LogDebug("Binding UDP server to {Address} on port {PortNumber}", intfAddress, PortNumber);
|
||||||
|
|
||||||
|
var server = new UdpServer(_logger, _appHost, _config, intfAddress, PortNumber);
|
||||||
|
server.Start(_cancellationTokenSource.Token);
|
||||||
|
_udpServers.Add(server);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SocketException ex)
|
catch (SocketException ex)
|
||||||
|
@ -119,7 +128,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
throw new ObjectDisposedException(this.GetType().Name);
|
throw new ObjectDisposedException(GetType().Name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Controller.Net.WebSocketMessages;
|
using MediaBrowser.Controller.Net.WebSocketMessages;
|
||||||
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
|
using MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
|
||||||
using MediaBrowser.Model.Session;
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.HttpServer
|
namespace Emby.Server.Implementations.HttpServer
|
||||||
|
|
|
@ -210,7 +210,6 @@ namespace Emby.Server.Implementations.IO
|
||||||
|
|
||||||
DisposeTimer();
|
DisposeTimer();
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,7 @@ namespace Emby.Server.Implementations.IO
|
||||||
}
|
}
|
||||||
|
|
||||||
// unc path
|
// unc path
|
||||||
if (filePath.StartsWith("\\\\", StringComparison.Ordinal))
|
if (filePath.StartsWith(@"\\", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
@ -103,15 +103,17 @@ namespace Emby.Server.Implementations.IO
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var filePathSpan = filePath.AsSpan();
|
||||||
|
|
||||||
// relative path
|
// relative path
|
||||||
if (firstChar == '\\')
|
if (firstChar == '\\')
|
||||||
{
|
{
|
||||||
filePath = filePath.Substring(1);
|
filePathSpan = filePathSpan.Slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Path.GetFullPath(Path.Combine(folderPath, filePath));
|
return Path.GetFullPath(Path.Join(folderPath, filePathSpan));
|
||||||
}
|
}
|
||||||
catch (ArgumentException)
|
catch (ArgumentException)
|
||||||
{
|
{
|
||||||
|
|
|
@ -34,7 +34,7 @@ namespace Emby.Server.Implementations.Images
|
||||||
Recursive = true,
|
Recursive = true,
|
||||||
DtoOptions = new DtoOptions(true),
|
DtoOptions = new DtoOptions(true),
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new ImageType[] { ImageType.Primary },
|
||||||
OrderBy = new (string, SortOrder)[]
|
OrderBy = new (ItemSortBy, SortOrder)[]
|
||||||
{
|
{
|
||||||
(ItemSortBy.IsFolder, SortOrder.Ascending),
|
(ItemSortBy.IsFolder, SortOrder.Ascending),
|
||||||
(ItemSortBy.SortName, SortOrder.Ascending)
|
(ItemSortBy.SortName, SortOrder.Ascending)
|
||||||
|
|
|
@ -30,47 +30,43 @@ namespace Emby.Server.Implementations.Images
|
||||||
|
|
||||||
BaseItemKind[] includeItemTypes;
|
BaseItemKind[] includeItemTypes;
|
||||||
|
|
||||||
if (string.Equals(viewType, CollectionType.Movies, StringComparison.Ordinal))
|
switch (viewType)
|
||||||
{
|
{
|
||||||
includeItemTypes = new[] { BaseItemKind.Movie };
|
case CollectionType.Movies:
|
||||||
}
|
includeItemTypes = new[] { BaseItemKind.Movie };
|
||||||
else if (string.Equals(viewType, CollectionType.TvShows, StringComparison.Ordinal))
|
break;
|
||||||
{
|
case CollectionType.TvShows:
|
||||||
includeItemTypes = new[] { BaseItemKind.Series };
|
includeItemTypes = new[] { BaseItemKind.Series };
|
||||||
}
|
break;
|
||||||
else if (string.Equals(viewType, CollectionType.Music, StringComparison.Ordinal))
|
case CollectionType.Music:
|
||||||
{
|
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
|
||||||
includeItemTypes = new[] { BaseItemKind.MusicAlbum };
|
break;
|
||||||
}
|
case CollectionType.MusicVideos:
|
||||||
else if (string.Equals(viewType, CollectionType.MusicVideos, StringComparison.Ordinal))
|
includeItemTypes = new[] { BaseItemKind.MusicVideo };
|
||||||
{
|
break;
|
||||||
includeItemTypes = new[] { BaseItemKind.MusicVideo };
|
case CollectionType.Books:
|
||||||
}
|
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
|
||||||
else if (string.Equals(viewType, CollectionType.Books, StringComparison.Ordinal))
|
break;
|
||||||
{
|
case CollectionType.BoxSets:
|
||||||
includeItemTypes = new[] { BaseItemKind.Book, BaseItemKind.AudioBook };
|
includeItemTypes = new[] { BaseItemKind.BoxSet };
|
||||||
}
|
break;
|
||||||
else if (string.Equals(viewType, CollectionType.BoxSets, StringComparison.Ordinal))
|
case CollectionType.HomeVideos:
|
||||||
{
|
case CollectionType.Photos:
|
||||||
includeItemTypes = new[] { BaseItemKind.BoxSet };
|
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
|
||||||
}
|
break;
|
||||||
else if (string.Equals(viewType, CollectionType.HomeVideos, StringComparison.Ordinal) || string.Equals(viewType, CollectionType.Photos, StringComparison.Ordinal))
|
default:
|
||||||
{
|
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
|
||||||
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Photo };
|
break;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
includeItemTypes = new[] { BaseItemKind.Video, BaseItemKind.Audio, BaseItemKind.Photo, BaseItemKind.Movie, BaseItemKind.Series };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var recursive = !string.Equals(CollectionType.Playlists, viewType, StringComparison.OrdinalIgnoreCase);
|
var recursive = viewType != CollectionType.Playlists;
|
||||||
|
|
||||||
return view.GetItemList(new InternalItemsQuery
|
return view.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
CollapseBoxSetItems = false,
|
CollapseBoxSetItems = false,
|
||||||
Recursive = recursive,
|
Recursive = recursive,
|
||||||
DtoOptions = new DtoOptions(false),
|
DtoOptions = new DtoOptions(false),
|
||||||
ImageTypes = new ImageType[] { ImageType.Primary },
|
ImageTypes = new[] { ImageType.Primary },
|
||||||
Limit = 8,
|
Limit = 8,
|
||||||
OrderBy = new[]
|
OrderBy = new[]
|
||||||
{
|
{
|
||||||
|
|
|
@ -36,7 +36,7 @@ namespace Emby.Server.Implementations.Images
|
||||||
var view = (UserView)item;
|
var view = (UserView)item;
|
||||||
|
|
||||||
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
var isUsingCollectionStrip = IsUsingCollectionStrip(view);
|
||||||
var recursive = isUsingCollectionStrip && !new[] { CollectionType.BoxSets, CollectionType.Playlists }.Contains(view.ViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
var recursive = isUsingCollectionStrip && view?.ViewType is not null && view.ViewType != CollectionType.BoxSets && view.ViewType != CollectionType.Playlists;
|
||||||
|
|
||||||
var result = view.GetItemList(new InternalItemsQuery
|
var result = view.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
|
@ -112,14 +112,14 @@ namespace Emby.Server.Implementations.Images
|
||||||
|
|
||||||
private static bool IsUsingCollectionStrip(UserView view)
|
private static bool IsUsingCollectionStrip(UserView view)
|
||||||
{
|
{
|
||||||
string[] collectionStripViewTypes =
|
CollectionType[] collectionStripViewTypes =
|
||||||
{
|
{
|
||||||
CollectionType.Movies,
|
CollectionType.Movies,
|
||||||
CollectionType.TvShows,
|
CollectionType.TvShows,
|
||||||
CollectionType.Playlists
|
CollectionType.Playlists
|
||||||
};
|
};
|
||||||
|
|
||||||
return collectionStripViewTypes.Contains(view.ViewType ?? string.Empty);
|
return view?.ViewType is not null && collectionStripViewTypes.Contains(view.ViewType.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
protected override string CreateImage(BaseItem item, IReadOnlyCollection<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
|
||||||
|
|
|
@ -46,7 +46,6 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Library;
|
using MediaBrowser.Model.Library;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
|
||||||
|
@ -526,14 +525,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
IDirectoryService directoryService,
|
IDirectoryService directoryService,
|
||||||
IItemResolver[] resolvers,
|
IItemResolver[] resolvers,
|
||||||
Folder parent = null,
|
Folder parent = null,
|
||||||
string collectionType = null,
|
CollectionType? collectionType = null,
|
||||||
LibraryOptions libraryOptions = null)
|
LibraryOptions libraryOptions = null)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(fileInfo);
|
ArgumentNullException.ThrowIfNull(fileInfo);
|
||||||
|
|
||||||
var fullPath = fileInfo.FullName;
|
var fullPath = fileInfo.FullName;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(collectionType) && parent is not null)
|
if (collectionType is null && parent is not null)
|
||||||
{
|
{
|
||||||
collectionType = GetContentTypeOverride(fullPath, true);
|
collectionType = GetContentTypeOverride(fullPath, true);
|
||||||
}
|
}
|
||||||
|
@ -636,7 +635,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
return !args.ContainsFileSystemEntryByName(".ignore");
|
return !args.ContainsFileSystemEntryByName(".ignore");
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, string collectionType = null)
|
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, CollectionType? collectionType = null)
|
||||||
{
|
{
|
||||||
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
return ResolvePaths(files, directoryService, parent, libraryOptions, collectionType, EntityResolvers);
|
||||||
}
|
}
|
||||||
|
@ -646,7 +645,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
IDirectoryService directoryService,
|
IDirectoryService directoryService,
|
||||||
Folder parent,
|
Folder parent,
|
||||||
LibraryOptions libraryOptions,
|
LibraryOptions libraryOptions,
|
||||||
string collectionType,
|
CollectionType? collectionType,
|
||||||
IItemResolver[] resolvers)
|
IItemResolver[] resolvers)
|
||||||
{
|
{
|
||||||
var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList();
|
var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList();
|
||||||
|
@ -676,7 +675,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
IReadOnlyList<FileSystemMetadata> fileList,
|
IReadOnlyList<FileSystemMetadata> fileList,
|
||||||
IDirectoryService directoryService,
|
IDirectoryService directoryService,
|
||||||
Folder parent,
|
Folder parent,
|
||||||
string collectionType,
|
CollectionType? collectionType,
|
||||||
IItemResolver[] resolvers,
|
IItemResolver[] resolvers,
|
||||||
LibraryOptions libraryOptions)
|
LibraryOptions libraryOptions)
|
||||||
{
|
{
|
||||||
|
@ -839,19 +838,12 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
var path = Person.GetPath(name);
|
var path = Person.GetPath(name);
|
||||||
var id = GetItemByNameId<Person>(path);
|
var id = GetItemByNameId<Person>(path);
|
||||||
if (GetItemById(id) is not Person item)
|
if (GetItemById(id) is Person item)
|
||||||
{
|
{
|
||||||
item = new Person
|
return item;
|
||||||
{
|
|
||||||
Name = name,
|
|
||||||
Id = id,
|
|
||||||
DateCreated = DateTime.UtcNow,
|
|
||||||
DateModified = DateTime.UtcNow,
|
|
||||||
Path = path
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return item;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1162,7 +1154,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
Name = Path.GetFileName(dir),
|
Name = Path.GetFileName(dir),
|
||||||
|
|
||||||
Locations = _fileSystem.GetFilePaths(dir, false)
|
Locations = _fileSystem.GetFilePaths(dir, false)
|
||||||
.Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase))
|
.Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
.Select(i =>
|
.Select(i =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1522,7 +1514,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
if (item is UserView view)
|
if (item is UserView view)
|
||||||
{
|
{
|
||||||
if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.Ordinal))
|
if (view.ViewType == CollectionType.LiveTv)
|
||||||
{
|
{
|
||||||
return new[] { view.Id };
|
return new[] { view.Id };
|
||||||
}
|
}
|
||||||
|
@ -1551,13 +1543,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle grouping
|
// Handle grouping
|
||||||
if (user is not null && !string.IsNullOrEmpty(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType)
|
if (user is not null && view.ViewType != CollectionType.Unknown && UserView.IsEligibleForGrouping(view.ViewType)
|
||||||
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
|
&& user.GetPreference(PreferenceKind.GroupedFolders).Length > 0)
|
||||||
{
|
{
|
||||||
return GetUserRootFolder()
|
return GetUserRootFolder()
|
||||||
.GetChildren(user, true)
|
.GetChildren(user, true)
|
||||||
.OfType<CollectionFolder>()
|
.OfType<CollectionFolder>()
|
||||||
.Where(i => string.IsNullOrEmpty(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
|
.Where(i => i.CollectionType is null || i.CollectionType == view.ViewType)
|
||||||
.Where(i => user.IsFolderGrouped(i.Id))
|
.Where(i => user.IsFolderGrouped(i.Id))
|
||||||
.SelectMany(i => GetTopParentIdsForQuery(i, user));
|
.SelectMany(i => GetTopParentIdsForQuery(i, user));
|
||||||
}
|
}
|
||||||
|
@ -1686,7 +1678,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// <param name="sortBy">The sort by.</param>
|
/// <param name="sortBy">The sort by.</param>
|
||||||
/// <param name="sortOrder">The sort order.</param>
|
/// <param name="sortOrder">The sort order.</param>
|
||||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder)
|
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder)
|
||||||
{
|
{
|
||||||
var isFirst = true;
|
var isFirst = true;
|
||||||
|
|
||||||
|
@ -1709,7 +1701,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
return orderedItems ?? items;
|
return orderedItems ?? items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
|
public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy)
|
||||||
{
|
{
|
||||||
var isFirst = true;
|
var isFirst = true;
|
||||||
|
|
||||||
|
@ -1744,9 +1736,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
/// <param name="name">The name.</param>
|
/// <param name="name">The name.</param>
|
||||||
/// <param name="user">The user.</param>
|
/// <param name="user">The user.</param>
|
||||||
/// <returns>IBaseItemComparer.</returns>
|
/// <returns>IBaseItemComparer.</returns>
|
||||||
private IBaseItemComparer GetComparer(string name, User user)
|
private IBaseItemComparer GetComparer(ItemSortBy name, User user)
|
||||||
{
|
{
|
||||||
var comparer = Comparers.FirstOrDefault(c => string.Equals(name, c.Name, StringComparison.OrdinalIgnoreCase));
|
var comparer = Comparers.FirstOrDefault(c => name == c.Type);
|
||||||
|
|
||||||
// If it requires a user, create a new one, and assign the user
|
// If it requires a user, create a new one, and assign the user
|
||||||
if (comparer is IUserBaseItemComparer)
|
if (comparer is IUserBaseItemComparer)
|
||||||
|
@ -2073,16 +2065,16 @@ namespace Emby.Server.Implementations.Library
|
||||||
: collectionFolder.GetLibraryOptions();
|
: collectionFolder.GetLibraryOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetContentType(BaseItem item)
|
public CollectionType? GetContentType(BaseItem item)
|
||||||
{
|
{
|
||||||
string configuredContentType = GetConfiguredContentType(item, false);
|
var configuredContentType = GetConfiguredContentType(item, false);
|
||||||
if (!string.IsNullOrEmpty(configuredContentType))
|
if (configuredContentType is not null)
|
||||||
{
|
{
|
||||||
return configuredContentType;
|
return configuredContentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
configuredContentType = GetConfiguredContentType(item, true);
|
configuredContentType = GetConfiguredContentType(item, true);
|
||||||
if (!string.IsNullOrEmpty(configuredContentType))
|
if (configuredContentType is not null)
|
||||||
{
|
{
|
||||||
return configuredContentType;
|
return configuredContentType;
|
||||||
}
|
}
|
||||||
|
@ -2090,31 +2082,31 @@ namespace Emby.Server.Implementations.Library
|
||||||
return GetInheritedContentType(item);
|
return GetInheritedContentType(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetInheritedContentType(BaseItem item)
|
public CollectionType? GetInheritedContentType(BaseItem item)
|
||||||
{
|
{
|
||||||
var type = GetTopFolderContentType(item);
|
var type = GetTopFolderContentType(item);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(type))
|
if (type is not null)
|
||||||
{
|
{
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
return item.GetParents()
|
return item.GetParents()
|
||||||
.Select(GetConfiguredContentType)
|
.Select(GetConfiguredContentType)
|
||||||
.LastOrDefault(i => !string.IsNullOrEmpty(i));
|
.LastOrDefault(i => i is not null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetConfiguredContentType(BaseItem item)
|
public CollectionType? GetConfiguredContentType(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetConfiguredContentType(item, false);
|
return GetConfiguredContentType(item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetConfiguredContentType(string path)
|
public CollectionType? GetConfiguredContentType(string path)
|
||||||
{
|
{
|
||||||
return GetContentTypeOverride(path, false);
|
return GetContentTypeOverride(path, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath)
|
public CollectionType? GetConfiguredContentType(BaseItem item, bool inheritConfiguredPath)
|
||||||
{
|
{
|
||||||
if (item is ICollectionFolder collectionFolder)
|
if (item is ICollectionFolder collectionFolder)
|
||||||
{
|
{
|
||||||
|
@ -2124,16 +2116,21 @@ namespace Emby.Server.Implementations.Library
|
||||||
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
|
return GetContentTypeOverride(item.ContainingFolderPath, inheritConfiguredPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetContentTypeOverride(string path, bool inherit)
|
private CollectionType? GetContentTypeOverride(string path, bool inherit)
|
||||||
{
|
{
|
||||||
var nameValuePair = _configurationManager.Configuration.ContentTypes
|
var nameValuePair = _configurationManager.Configuration.ContentTypes
|
||||||
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|
.FirstOrDefault(i => _fileSystem.AreEqual(i.Name, path)
|
||||||
|| (inherit && !string.IsNullOrEmpty(i.Name)
|
|| (inherit && !string.IsNullOrEmpty(i.Name)
|
||||||
&& _fileSystem.ContainsSubPath(i.Name, path)));
|
&& _fileSystem.ContainsSubPath(i.Name, path)));
|
||||||
return nameValuePair?.Value;
|
if (Enum.TryParse<CollectionType>(nameValuePair?.Value, out var collectionType))
|
||||||
|
{
|
||||||
|
return collectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTopFolderContentType(BaseItem item)
|
private CollectionType? GetTopFolderContentType(BaseItem item)
|
||||||
{
|
{
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
|
@ -2155,13 +2152,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
.OfType<ICollectionFolder>()
|
.OfType<ICollectionFolder>()
|
||||||
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path))
|
.Where(i => string.Equals(i.Path, item.Path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(item.Path))
|
||||||
.Select(i => i.CollectionType)
|
.Select(i => i.CollectionType)
|
||||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
.FirstOrDefault(i => i is not null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserView GetNamedView(
|
public UserView GetNamedView(
|
||||||
User user,
|
User user,
|
||||||
string name,
|
string name,
|
||||||
string viewType,
|
CollectionType? viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
return GetNamedView(user, name, Guid.Empty, viewType, sortName);
|
return GetNamedView(user, name, Guid.Empty, viewType, sortName);
|
||||||
|
@ -2169,13 +2166,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public UserView GetNamedView(
|
public UserView GetNamedView(
|
||||||
string name,
|
string name,
|
||||||
string viewType,
|
CollectionType viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
var path = Path.Combine(
|
var path = Path.Combine(
|
||||||
_configurationManager.ApplicationPaths.InternalMetadataPath,
|
_configurationManager.ApplicationPaths.InternalMetadataPath,
|
||||||
"views",
|
"views",
|
||||||
_fileSystem.GetValidFilename(viewType));
|
_fileSystem.GetValidFilename(viewType.ToString()));
|
||||||
|
|
||||||
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
|
var id = GetNewItemId(path + "_namedview_" + name, typeof(UserView));
|
||||||
|
|
||||||
|
@ -2215,13 +2212,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
User user,
|
User user,
|
||||||
string name,
|
string name,
|
||||||
Guid parentId,
|
Guid parentId,
|
||||||
string viewType,
|
CollectionType? viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
var parentIdString = parentId.Equals(default)
|
var parentIdString = parentId.Equals(default)
|
||||||
? null
|
? null
|
||||||
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
var idValues = "38_namedview_" + name + user.Id.ToString("N", CultureInfo.InvariantCulture) + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
|
||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
var id = GetNewItemId(idValues, typeof(UserView));
|
||||||
|
|
||||||
|
@ -2277,7 +2274,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
public UserView GetShadowView(
|
public UserView GetShadowView(
|
||||||
BaseItem parent,
|
BaseItem parent,
|
||||||
string viewType,
|
CollectionType? viewType,
|
||||||
string sortName)
|
string sortName)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(parent);
|
ArgumentNullException.ThrowIfNull(parent);
|
||||||
|
@ -2285,7 +2282,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
var name = parent.Name;
|
var name = parent.Name;
|
||||||
var parentId = parent.Id;
|
var parentId = parent.Id;
|
||||||
|
|
||||||
var idValues = "38_namedview_" + name + parentId + (viewType ?? string.Empty);
|
var idValues = "38_namedview_" + name + parentId + (viewType?.ToString() ?? string.Empty);
|
||||||
|
|
||||||
var id = GetNewItemId(idValues, typeof(UserView));
|
var id = GetNewItemId(idValues, typeof(UserView));
|
||||||
|
|
||||||
|
@ -2342,7 +2339,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
public UserView GetNamedView(
|
public UserView GetNamedView(
|
||||||
string name,
|
string name,
|
||||||
Guid parentId,
|
Guid parentId,
|
||||||
string viewType,
|
CollectionType? viewType,
|
||||||
string sortName,
|
string sortName,
|
||||||
string uniqueId)
|
string uniqueId)
|
||||||
{
|
{
|
||||||
|
@ -2351,7 +2348,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
var parentIdString = parentId.Equals(default)
|
var parentIdString = parentId.Equals(default)
|
||||||
? null
|
? null
|
||||||
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
: parentId.ToString("N", CultureInfo.InvariantCulture);
|
||||||
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType ?? string.Empty);
|
var idValues = "37_namedview_" + name + (parentIdString ?? string.Empty) + (viewType?.ToString() ?? string.Empty);
|
||||||
if (!string.IsNullOrEmpty(uniqueId))
|
if (!string.IsNullOrEmpty(uniqueId))
|
||||||
{
|
{
|
||||||
idValues += uniqueId;
|
idValues += uniqueId;
|
||||||
|
@ -2386,7 +2383,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
|
if (viewType != item.ViewType)
|
||||||
{
|
{
|
||||||
item.ViewType = viewType;
|
item.ViewType = viewType;
|
||||||
item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).GetAwaiter().GetResult();
|
||||||
|
@ -2858,7 +2855,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
|
var path = Path.Combine(virtualFolderPath, collectionType.ToString().ToLowerInvariant() + ".collection");
|
||||||
|
|
||||||
File.WriteAllBytes(path, Array.Empty<byte>());
|
await File.WriteAllBytesAsync(path, Array.Empty<byte>()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
|
CollectionFolder.SaveLibraryOptions(virtualFolderPath, options);
|
||||||
|
@ -2900,9 +2897,18 @@ namespace Emby.Server.Implementations.Library
|
||||||
var saveEntity = false;
|
var saveEntity = false;
|
||||||
var personEntity = GetPerson(person.Name);
|
var personEntity = GetPerson(person.Name);
|
||||||
|
|
||||||
// if PresentationUniqueKey is empty it's likely a new item.
|
if (personEntity is null)
|
||||||
if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
|
|
||||||
{
|
{
|
||||||
|
var path = Person.GetPath(person.Name);
|
||||||
|
personEntity = new Person()
|
||||||
|
{
|
||||||
|
Name = person.Name,
|
||||||
|
Id = GetItemByNameId<Person>(path),
|
||||||
|
DateCreated = DateTime.UtcNow,
|
||||||
|
DateModified = DateTime.UtcNow,
|
||||||
|
Path = path
|
||||||
|
};
|
||||||
|
|
||||||
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
|
personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
|
||||||
saveEntity = true;
|
saveEntity = true;
|
||||||
}
|
}
|
||||||
|
@ -3135,7 +3141,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
}
|
}
|
||||||
|
|
||||||
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
|
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
|
||||||
.Where(i => string.Equals(ShortcutFileExtension, Path.GetExtension(i), StringComparison.OrdinalIgnoreCase))
|
.Where(i => Path.GetExtension(i.AsSpan()).Equals(ShortcutFileExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
.FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
|
.FirstOrDefault(f => _appHost.ExpandVirtualPath(_fileSystem.ResolveShortcut(f)).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(shortcut))
|
if (!string.IsNullOrEmpty(shortcut))
|
||||||
|
|
|
@ -48,15 +48,20 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cacheKey))
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
|
FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
|
||||||
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// _logger.LogDebug("Found cached media info");
|
// _logger.LogDebug("Found cached media info");
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogError(ex, "Error deserializing mediainfo cache");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await jsonStream.DisposeAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +89,13 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (cacheFilePath is not null)
|
if (cacheFilePath is not null)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||||
await using FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
|
FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
|
||||||
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
await using (createStream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
_logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ using System.Linq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using EasyCaching.Core.Configurations;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions.Json;
|
using Jellyfin.Extensions.Json;
|
||||||
|
@ -186,11 +187,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (item.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
else if (item.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
||||||
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||||
|
@ -334,11 +335,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
{
|
{
|
||||||
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
SetDefaultAudioAndSubtitleStreamIndexes(item, source, user);
|
||||||
|
|
||||||
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
if (item.MediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
|
||||||
}
|
}
|
||||||
else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
else if (item.MediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
|
||||||
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
|
||||||
|
@ -417,9 +418,9 @@ namespace Emby.Server.Implementations.Library
|
||||||
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
public void SetDefaultAudioAndSubtitleStreamIndexes(BaseItem item, MediaSourceInfo source, User user)
|
||||||
{
|
{
|
||||||
// Item would only be null if the app didn't supply ItemId as part of the live stream open request
|
// Item would only be null if the app didn't supply ItemId as part of the live stream open request
|
||||||
var mediaType = item is null ? MediaType.Video : item.MediaType;
|
var mediaType = item?.MediaType ?? MediaType.Video;
|
||||||
|
|
||||||
if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
|
if (mediaType == MediaType.Video)
|
||||||
{
|
{
|
||||||
var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item);
|
var userData = item is null ? new UserItemData() : _userDataManager.GetUserData(user, item);
|
||||||
|
|
||||||
|
@ -428,7 +429,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
|
SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
|
||||||
SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
|
SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
|
||||||
}
|
}
|
||||||
else if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
|
else if (mediaType == MediaType.Audio)
|
||||||
{
|
{
|
||||||
var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||||
|
|
||||||
|
@ -625,17 +626,19 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cacheKey))
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
|
FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
|
|
||||||
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// _logger.LogDebug("Found cached media info");
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(ex, "_jsonSerializer.DeserializeFromFile threw an exception.");
|
_logger.LogDebug(ex, "_jsonSerializer.DeserializeFromFile threw an exception.");
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await jsonStream.DisposeAsync().ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaInfo is null)
|
if (mediaInfo is null)
|
||||||
|
@ -664,8 +667,11 @@ namespace Emby.Server.Implementations.Library
|
||||||
if (cacheFilePath is not null)
|
if (cacheFilePath is not null)
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath));
|
||||||
await using FileStream createStream = File.Create(cacheFilePath);
|
FileStream createStream = File.Create(cacheFilePath);
|
||||||
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
await using (createStream.ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
// _logger.LogDebug("Saved media info to {0}", cacheFilePath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ using Emby.Naming.Audio;
|
||||||
using Emby.Naming.AudioBook;
|
using Emby.Naming.AudioBook;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
using MediaBrowser.Model.Entities;
|
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
public MultiItemResolverResult ResolveMultiple(
|
public MultiItemResolverResult ResolveMultiple(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
CollectionType? collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var result = ResolveMultipleInternal(parent, files, collectionType);
|
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||||
|
@ -59,9 +59,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
private MultiItemResolverResult ResolveMultipleInternal(
|
private MultiItemResolverResult ResolveMultipleInternal(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType)
|
CollectionType? collectionType)
|
||||||
{
|
{
|
||||||
if (string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.Books)
|
||||||
{
|
{
|
||||||
return ResolveMultipleAudio(parent, files, true);
|
return ResolveMultipleAudio(parent, files, true);
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
var isBooksCollectionType = string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase);
|
var isBooksCollectionType = collectionType == CollectionType.Books;
|
||||||
|
|
||||||
if (args.IsDirectory)
|
if (args.IsDirectory)
|
||||||
{
|
{
|
||||||
|
@ -94,15 +94,15 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
|
if (AudioFileParser.IsAudioFile(args.Path, _namingOptions))
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(args.Path);
|
var extension = Path.GetExtension(args.Path.AsSpan());
|
||||||
|
|
||||||
if (string.Equals(extension, ".cue", StringComparison.OrdinalIgnoreCase))
|
if (extension.Equals(".cue", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// if audio file exists of same name, return null
|
// if audio file exists of same name, return null
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isMixedCollectionType = string.IsNullOrEmpty(collectionType);
|
var isMixedCollectionType = collectionType is null;
|
||||||
|
|
||||||
// For conflicting extensions, give priority to videos
|
// For conflicting extensions, give priority to videos
|
||||||
if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
|
if (isMixedCollectionType && VideoResolver.IsVideoFile(args.Path, _namingOptions))
|
||||||
|
@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
MediaBrowser.Controller.Entities.Audio.Audio item = null;
|
MediaBrowser.Controller.Entities.Audio.Audio item = null;
|
||||||
|
|
||||||
var isMusicCollectionType = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
|
var isMusicCollectionType = collectionType == CollectionType.Music;
|
||||||
|
|
||||||
// Use regular audio type for mixed libraries, owned items and music
|
// Use regular audio type for mixed libraries, owned items and music
|
||||||
if (isMixedCollectionType ||
|
if (isMixedCollectionType ||
|
||||||
|
@ -128,7 +128,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
if (item is not null)
|
if (item is not null)
|
||||||
{
|
{
|
||||||
item.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
|
item.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
item.IsInMixedFolder = true;
|
item.IsInMixedFolder = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Audio;
|
using Emby.Naming.Audio;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -54,7 +55,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
protected override MusicAlbum Resolve(ItemResolveArgs args)
|
protected override MusicAlbum Resolve(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
|
var isMusicMediaFolder = collectionType == CollectionType.Music;
|
||||||
|
|
||||||
// If there's a collection type and it's not music, don't allow it.
|
// If there's a collection type and it's not music, don't allow it.
|
||||||
if (!isMusicMediaFolder)
|
if (!isMusicMediaFolder)
|
||||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities.Audio;
|
using MediaBrowser.Controller.Entities.Audio;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -64,7 +65,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
||||||
|
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
|
var isMusicMediaFolder = collectionType == CollectionType.Music;
|
||||||
|
|
||||||
// If there's a collection type and it's not music, it can't be a music artist
|
// If there's a collection type and it's not music, it can't be a music artist
|
||||||
if (!isMusicMediaFolder)
|
if (!isMusicMediaFolder)
|
||||||
|
|
|
@ -263,7 +263,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return directoryService.GetFilePaths(fullPath).Any(i => string.Equals(Path.GetExtension(i), ".vob", StringComparison.OrdinalIgnoreCase));
|
return directoryService.GetFilePaths(fullPath).Any(i => Path.GetExtension(i.AsSpan()).Equals(".vob", StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -22,7 +23,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
// Only process items that are in a collection folder containing books
|
// Only process items that are in a collection folder containing books
|
||||||
if (!string.Equals(collectionType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
|
if (collectionType != CollectionType.Books)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -32,9 +33,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||||
return GetBook(args);
|
return GetBook(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
var extension = Path.GetExtension(args.Path);
|
var extension = Path.GetExtension(args.Path.AsSpan());
|
||||||
|
|
||||||
if (extension is not null && _validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
if (_validExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// It's a book
|
// It's a book
|
||||||
return new Book
|
return new Book
|
||||||
|
@ -51,12 +52,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
|
||||||
{
|
{
|
||||||
var bookFiles = args.FileSystemChildren.Where(f =>
|
var bookFiles = args.FileSystemChildren.Where(f =>
|
||||||
{
|
{
|
||||||
var fileExtension = Path.GetExtension(f.FullName)
|
var fileExtension = Path.GetExtension(f.FullName.AsSpan());
|
||||||
?? string.Empty;
|
|
||||||
|
|
||||||
return _validExtensions.Contains(
|
return _validExtensions.Contains(
|
||||||
fileExtension,
|
fileExtension,
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparison.OrdinalIgnoreCase);
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// Don't return a Book if there is more (or less) than one document in the directory
|
// Don't return a Book if there is more (or less) than one document in the directory
|
||||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -28,13 +29,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
{
|
{
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
private string[] _validCollectionTypes = new[]
|
private static readonly CollectionType[] _validCollectionTypes = new[]
|
||||||
{
|
{
|
||||||
CollectionType.Movies,
|
CollectionType.Movies,
|
||||||
CollectionType.HomeVideos,
|
CollectionType.HomeVideos,
|
||||||
CollectionType.MusicVideos,
|
CollectionType.MusicVideos,
|
||||||
CollectionType.TvShows,
|
CollectionType.TvShows,
|
||||||
CollectionType.Photos
|
CollectionType.Photos
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -63,7 +64,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
public MultiItemResolverResult ResolveMultiple(
|
public MultiItemResolverResult ResolveMultiple(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType,
|
CollectionType? collectionType,
|
||||||
IDirectoryService directoryService)
|
IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
var result = ResolveMultipleInternal(parent, files, collectionType);
|
var result = ResolveMultipleInternal(parent, files, collectionType);
|
||||||
|
@ -99,17 +100,17 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
Video movie = null;
|
Video movie = null;
|
||||||
var files = args.GetActualFileSystemChildren().ToList();
|
var files = args.GetActualFileSystemChildren().ToList();
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.MusicVideos)
|
||||||
{
|
{
|
||||||
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
|
movie = FindMovie<MusicVideo>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.HomeVideos)
|
||||||
{
|
{
|
||||||
movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
|
movie = FindMovie<Video>(args, args.Path, args.Parent, files, DirectoryService, collectionType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(collectionType))
|
if (collectionType is null)
|
||||||
{
|
{
|
||||||
// Owned items will be caught by the video extra resolver
|
// Owned items will be caught by the video extra resolver
|
||||||
if (args.Parent is null)
|
if (args.Parent is null)
|
||||||
|
@ -125,7 +126,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
|
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.Movies)
|
||||||
{
|
{
|
||||||
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
|
movie = FindMovie<Movie>(args, args.Path, args.Parent, files, DirectoryService, collectionType, true);
|
||||||
}
|
}
|
||||||
|
@ -146,22 +147,21 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
|
|
||||||
Video item = null;
|
Video item = null;
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.MusicVideos)
|
||||||
{
|
{
|
||||||
item = ResolveVideo<MusicVideo>(args, false);
|
item = ResolveVideo<MusicVideo>(args, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// To find a movie file, the collection type must be movies or boxsets
|
// To find a movie file, the collection type must be movies or boxsets
|
||||||
else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
else if (collectionType == CollectionType.Movies)
|
||||||
{
|
{
|
||||||
item = ResolveVideo<Movie>(args, true);
|
item = ResolveVideo<Movie>(args, true);
|
||||||
}
|
}
|
||||||
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
else if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos)
|
||||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
item = ResolveVideo<Video>(args, false);
|
item = ResolveVideo<Video>(args, false);
|
||||||
}
|
}
|
||||||
else if (string.IsNullOrEmpty(collectionType))
|
else if (collectionType is null)
|
||||||
{
|
{
|
||||||
if (args.HasParent<Series>())
|
if (args.HasParent<Series>())
|
||||||
{
|
{
|
||||||
|
@ -188,25 +188,24 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
private MultiItemResolverResult ResolveMultipleInternal(
|
private MultiItemResolverResult ResolveMultipleInternal(
|
||||||
Folder parent,
|
Folder parent,
|
||||||
List<FileSystemMetadata> files,
|
List<FileSystemMetadata> files,
|
||||||
string collectionType)
|
CollectionType? collectionType)
|
||||||
{
|
{
|
||||||
if (IsInvalid(parent, collectionType))
|
if (IsInvalid(parent, collectionType))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
|
if (collectionType is CollectionType.MusicVideos)
|
||||||
{
|
{
|
||||||
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
|
return ResolveVideos<MusicVideo>(parent, files, true, collectionType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
|
if (collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos)
|
||||||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
return ResolveVideos<Video>(parent, files, false, collectionType, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(collectionType))
|
if (collectionType is null)
|
||||||
{
|
{
|
||||||
// Owned items should just use the plain video type
|
// Owned items should just use the plain video type
|
||||||
if (parent is null)
|
if (parent is null)
|
||||||
|
@ -222,12 +221,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return ResolveVideos<Movie>(parent, files, false, collectionType, true);
|
return ResolveVideos<Movie>(parent, files, false, collectionType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.Movies)
|
||||||
{
|
{
|
||||||
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
|
return ResolveVideos<Movie>(parent, files, true, collectionType, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.TvShows)
|
||||||
{
|
{
|
||||||
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
|
return ResolveVideos<Episode>(parent, files, false, collectionType, true);
|
||||||
}
|
}
|
||||||
|
@ -239,13 +238,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
Folder parent,
|
Folder parent,
|
||||||
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
IEnumerable<FileSystemMetadata> fileSystemEntries,
|
||||||
bool supportMultiEditions,
|
bool supportMultiEditions,
|
||||||
string collectionType,
|
CollectionType? collectionType,
|
||||||
bool parseName)
|
bool parseName)
|
||||||
where T : Video, new()
|
where T : Video, new()
|
||||||
{
|
{
|
||||||
var files = new List<FileSystemMetadata>();
|
var files = new List<FileSystemMetadata>();
|
||||||
var leftOver = new List<FileSystemMetadata>();
|
var leftOver = new List<FileSystemMetadata>();
|
||||||
var hasCollectionType = !string.IsNullOrEmpty(collectionType);
|
var hasCollectionType = collectionType is not null;
|
||||||
|
|
||||||
// Loop through each child file/folder and see if we find a video
|
// Loop through each child file/folder and see if we find a video
|
||||||
foreach (var child in fileSystemEntries)
|
foreach (var child in fileSystemEntries)
|
||||||
|
@ -398,13 +397,13 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
/// Finds a movie based on a child file system entries.
|
/// Finds a movie based on a child file system entries.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Movie.</returns>
|
/// <returns>Movie.</returns>
|
||||||
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool parseName)
|
private T FindMovie<T>(ItemResolveArgs args, string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, CollectionType? collectionType, bool parseName)
|
||||||
where T : Video, new()
|
where T : Video, new()
|
||||||
{
|
{
|
||||||
var multiDiscFolders = new List<FileSystemMetadata>();
|
var multiDiscFolders = new List<FileSystemMetadata>();
|
||||||
|
|
||||||
var libraryOptions = args.LibraryOptions;
|
var libraryOptions = args.LibraryOptions;
|
||||||
var supportPhotos = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && libraryOptions.EnablePhotos;
|
var supportPhotos = collectionType == CollectionType.HomeVideos && libraryOptions.EnablePhotos;
|
||||||
var photos = new List<FileSystemMetadata>();
|
var photos = new List<FileSystemMetadata>();
|
||||||
|
|
||||||
// Search for a folder rip
|
// Search for a folder rip
|
||||||
|
@ -460,8 +459,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
|
var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
|
||||||
new MultiItemResolverResult();
|
new MultiItemResolverResult();
|
||||||
|
|
||||||
var isPhotosCollection = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)
|
var isPhotosCollection = collectionType == CollectionType.HomeVideos || collectionType == CollectionType.Photos;
|
||||||
|| string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase);
|
|
||||||
if (!isPhotosCollection && result.Items.Count == 1)
|
if (!isPhotosCollection && result.Items.Count == 1)
|
||||||
{
|
{
|
||||||
var videoPath = result.Items[0].Path;
|
var videoPath = result.Items[0].Path;
|
||||||
|
@ -562,7 +560,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
return returnVideo;
|
return returnVideo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsInvalid(Folder parent, ReadOnlySpan<char> collectionType)
|
private bool IsInvalid(Folder parent, CollectionType? collectionType)
|
||||||
{
|
{
|
||||||
if (parent is not null)
|
if (parent is not null)
|
||||||
{
|
{
|
||||||
|
@ -572,12 +570,12 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collectionType.IsEmpty)
|
if (collectionType is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !_validCollectionTypes.Contains(collectionType, StringComparison.OrdinalIgnoreCase);
|
return !_validCollectionTypes.Contains(collectionType.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -45,8 +46,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
// Must be an image file within a photo collection
|
// Must be an image file within a photo collection
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
|
if (collectionType == CollectionType.Photos
|
||||||
|| (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
|
|| (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos))
|
||||||
{
|
{
|
||||||
if (HasPhotos(args))
|
if (HasPhotos(args))
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Drawing;
|
using MediaBrowser.Controller.Drawing;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
|
@ -61,8 +61,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
// Must be an image file within a photo collection
|
// Must be an image file within a photo collection
|
||||||
var collectionType = args.CollectionType;
|
var collectionType = args.CollectionType;
|
||||||
|
|
||||||
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase)
|
if (collectionType == CollectionType.Photos
|
||||||
|| (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.LibraryOptions.EnablePhotos))
|
|| (collectionType == CollectionType.HomeVideos && args.LibraryOptions.EnablePhotos))
|
||||||
{
|
{
|
||||||
if (IsImageFile(args.Path, _imageProcessor))
|
if (IsImageFile(args.Path, _imageProcessor))
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Playlists;
|
using MediaBrowser.Controller.Playlists;
|
||||||
|
@ -19,9 +20,9 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PlaylistResolver : GenericFolderResolver<Playlist>
|
public class PlaylistResolver : GenericFolderResolver<Playlist>
|
||||||
{
|
{
|
||||||
private string[] _musicPlaylistCollectionTypes =
|
private CollectionType?[] _musicPlaylistCollectionTypes =
|
||||||
{
|
{
|
||||||
string.Empty,
|
null,
|
||||||
CollectionType.Music
|
CollectionType.Music
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
|
|
||||||
// Check if this is a music playlist file
|
// Check if this is a music playlist file
|
||||||
// It should have the correct collection type and a supported file extension
|
// It should have the correct collection type and a supported file extension
|
||||||
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
else if (_musicPlaylistCollectionTypes.Contains(args.CollectionType))
|
||||||
{
|
{
|
||||||
var extension = Path.GetExtension(args.Path.AsSpan());
|
var extension = Path.GetExtension(args.Path.AsSpan());
|
||||||
if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
if (Playlist.SupportedExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
|
@ -62,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCollectionType(ItemResolveArgs args)
|
private CollectionType? GetCollectionType(ItemResolveArgs args)
|
||||||
{
|
{
|
||||||
return args.FileSystemChildren
|
return args.FileSystemChildren
|
||||||
.Where(i =>
|
.Where(i =>
|
||||||
|
@ -78,7 +79,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
|
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
|
||||||
.FirstOrDefault();
|
.Select(i => Enum.TryParse<CollectionType>(i, out var collectionType) ? collectionType : (CollectionType?)null)
|
||||||
|
.FirstOrDefault(i => i is not null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
|
@ -48,9 +49,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
|
|
||||||
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
|
// If the parent is a Season or Series and the parent is not an extras folder, then this is an Episode if the VideoResolver returns something
|
||||||
// Also handle flat tv folders
|
// Also handle flat tv folders
|
||||||
if (season is not null ||
|
if (season is not null
|
||||||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
|
|| args.GetCollectionType() == CollectionType.TvShows
|
||||||
args.HasParent<Series>())
|
|| args.HasParent<Series>())
|
||||||
{
|
{
|
||||||
var episode = ResolveVideo<Episode>(args, false);
|
var episode = ResolveVideo<Episode>(args, false);
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
var resolver = new Naming.TV.EpisodeResolver(namingOptions);
|
var resolver = new Naming.TV.EpisodeResolver(namingOptions);
|
||||||
|
|
||||||
var folderName = System.IO.Path.GetFileName(path);
|
var folderName = System.IO.Path.GetFileName(path);
|
||||||
var testPath = "\\\\test\\" + folderName;
|
var testPath = @"\\test\" + folderName;
|
||||||
|
|
||||||
var episodeInfo = resolver.Resolve(testPath, true);
|
var episodeInfo = resolver.Resolve(testPath, true);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ using System.IO;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Naming.TV;
|
using Emby.Naming.TV;
|
||||||
using Emby.Naming.Video;
|
using Emby.Naming.Video;
|
||||||
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.Resolvers;
|
using MediaBrowser.Controller.Resolvers;
|
||||||
|
@ -59,11 +60,11 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
|
var seriesInfo = Naming.TV.SeriesResolver.Resolve(_namingOptions, args.Path);
|
||||||
|
|
||||||
var collectionType = args.GetCollectionType();
|
var collectionType = args.GetCollectionType();
|
||||||
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
if (collectionType == CollectionType.TvShows)
|
||||||
{
|
{
|
||||||
// TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
|
// TODO refactor into separate class or something, this is copied from LibraryManager.GetConfiguredContentType
|
||||||
var configuredContentType = args.GetConfiguredContentType();
|
var configuredContentType = args.GetConfiguredContentType();
|
||||||
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
|
if (configuredContentType != CollectionType.TvShows)
|
||||||
{
|
{
|
||||||
return new Series
|
return new Series
|
||||||
{
|
{
|
||||||
|
@ -72,7 +73,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.TV
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (string.IsNullOrEmpty(collectionType))
|
else if (collectionType is null)
|
||||||
{
|
{
|
||||||
if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
|
if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using Jellyfin.Extensions;
|
|
||||||
using MediaBrowser.Controller.Channels;
|
using MediaBrowser.Controller.Channels;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
|
@ -64,8 +63,8 @@ namespace Emby.Server.Implementations.Library
|
||||||
var collectionFolder = folder as ICollectionFolder;
|
var collectionFolder = folder as ICollectionFolder;
|
||||||
var folderViewType = collectionFolder?.CollectionType;
|
var folderViewType = collectionFolder?.CollectionType;
|
||||||
|
|
||||||
// Playlist library requires special handling because the folder only refrences user playlists
|
// Playlist library requires special handling because the folder only references user playlists
|
||||||
if (string.Equals(folderViewType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
if (folderViewType == CollectionType.Playlists)
|
||||||
{
|
{
|
||||||
var items = folder.GetItemList(new InternalItemsQuery(user)
|
var items = folder.GetItemList(new InternalItemsQuery(user)
|
||||||
{
|
{
|
||||||
|
@ -90,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
|
if (query.PresetViews.Contains(folderViewType))
|
||||||
{
|
{
|
||||||
list.Add(GetUserView(folder, folderViewType, string.Empty));
|
list.Add(GetUserView(folder, folderViewType, string.Empty));
|
||||||
}
|
}
|
||||||
|
@ -102,14 +101,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows })
|
foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows })
|
||||||
{
|
{
|
||||||
var parents = groupedFolders.Where(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrEmpty(i.CollectionType))
|
var parents = groupedFolders.Where(i => i.CollectionType == viewType || i.CollectionType is null)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (parents.Count > 0)
|
if (parents.Count > 0)
|
||||||
{
|
{
|
||||||
var localizationKey = string.Equals(viewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ?
|
var localizationKey = viewType == CollectionType.TvShows
|
||||||
"TvShows" :
|
? "TvShows"
|
||||||
"Movies";
|
: "Movies";
|
||||||
|
|
||||||
list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews));
|
list.Add(GetUserView(parents, viewType, localizationKey, string.Empty, user, query.PresetViews));
|
||||||
}
|
}
|
||||||
|
@ -164,14 +163,14 @@ namespace Emby.Server.Implementations.Library
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserView GetUserSubViewWithName(string name, Guid parentId, string type, string sortName)
|
public UserView GetUserSubViewWithName(string name, Guid parentId, CollectionType? type, string sortName)
|
||||||
{
|
{
|
||||||
var uniqueId = parentId + "subview" + type;
|
var uniqueId = parentId + "subview" + type;
|
||||||
|
|
||||||
return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId);
|
return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserView GetUserSubView(Guid parentId, string type, string localizationKey, string sortName)
|
public UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName)
|
||||||
{
|
{
|
||||||
var name = _localizationManager.GetLocalizedString(localizationKey);
|
var name = _localizationManager.GetLocalizedString(localizationKey);
|
||||||
|
|
||||||
|
@ -180,15 +179,15 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
private Folder GetUserView(
|
private Folder GetUserView(
|
||||||
List<ICollectionFolder> parents,
|
List<ICollectionFolder> parents,
|
||||||
string viewType,
|
CollectionType? viewType,
|
||||||
string localizationKey,
|
string localizationKey,
|
||||||
string sortName,
|
string sortName,
|
||||||
User user,
|
User user,
|
||||||
string[] presetViews)
|
CollectionType?[] presetViews)
|
||||||
{
|
{
|
||||||
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
|
if (parents.Count == 1 && parents.All(i => i.CollectionType == viewType))
|
||||||
{
|
{
|
||||||
if (!presetViews.Contains(viewType, StringComparison.OrdinalIgnoreCase))
|
if (!presetViews.Contains(viewType))
|
||||||
{
|
{
|
||||||
return (Folder)parents[0];
|
return (Folder)parents[0];
|
||||||
}
|
}
|
||||||
|
@ -200,7 +199,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
return _libraryManager.GetNamedView(user, name, viewType, sortName);
|
return _libraryManager.GetNamedView(user, name, viewType, sortName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserView GetUserView(Folder parent, string viewType, string sortName)
|
public UserView GetUserView(Folder parent, CollectionType? viewType, string sortName)
|
||||||
{
|
{
|
||||||
return _libraryManager.GetShadowView(parent, viewType, sortName);
|
return _libraryManager.GetShadowView(parent, viewType, sortName);
|
||||||
}
|
}
|
||||||
|
@ -280,7 +279,7 @@ namespace Emby.Server.Implementations.Library
|
||||||
|
|
||||||
var isPlayed = request.IsPlayed;
|
var isPlayed = request.IsPlayed;
|
||||||
|
|
||||||
if (parents.OfType<ICollectionFolder>().Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)))
|
if (parents.OfType<ICollectionFolder>().Any(i => i.CollectionType == CollectionType.Music))
|
||||||
{
|
{
|
||||||
isPlayed = null;
|
isPlayed = null;
|
||||||
}
|
}
|
||||||
|
@ -306,18 +305,18 @@ namespace Emby.Server.Implementations.Library
|
||||||
var hasCollectionType = parents.OfType<UserView>().ToArray();
|
var hasCollectionType = parents.OfType<UserView>().ToArray();
|
||||||
if (hasCollectionType.Length > 0)
|
if (hasCollectionType.Length > 0)
|
||||||
{
|
{
|
||||||
if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)))
|
if (hasCollectionType.All(i => i.CollectionType == CollectionType.Movies))
|
||||||
{
|
{
|
||||||
includeItemTypes = new[] { BaseItemKind.Movie };
|
includeItemTypes = new[] { BaseItemKind.Movie };
|
||||||
}
|
}
|
||||||
else if (hasCollectionType.All(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)))
|
else if (hasCollectionType.All(i => i.CollectionType == CollectionType.TvShows))
|
||||||
{
|
{
|
||||||
includeItemTypes = new[] { BaseItemKind.Episode };
|
includeItemTypes = new[] { BaseItemKind.Episode };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var mediaTypes = new List<string>();
|
var mediaTypes = new List<MediaType>();
|
||||||
|
|
||||||
if (includeItemTypes.Length == 0)
|
if (includeItemTypes.Length == 0)
|
||||||
{
|
{
|
||||||
|
|
|
@ -63,7 +63,7 @@ namespace Emby.Server.Implementations.Library.Validators
|
||||||
{
|
{
|
||||||
var movies = _libraryManager.GetItemList(new InternalItemsQuery
|
var movies = _libraryManager.GetItemList(new InternalItemsQuery
|
||||||
{
|
{
|
||||||
MediaTypes = new string[] { MediaType.Video },
|
MediaTypes = new[] { MediaType.Video },
|
||||||
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
IncludeItemTypes = new[] { BaseItemKind.Movie },
|
||||||
IsVirtualItem = false,
|
IsVirtualItem = false,
|
||||||
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
|
||||||
|
|
|
@ -1851,7 +1851,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
@ -1860,7 +1861,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
Async = true
|
Async = true
|
||||||
};
|
};
|
||||||
|
|
||||||
await using (var writer = XmlWriter.Create(stream, settings))
|
var writer = XmlWriter.Create(stream, settings);
|
||||||
|
await using (writer.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||||
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
|
await writer.WriteStartElementAsync(null, "tvshow", null).ConfigureAwait(false);
|
||||||
|
@ -1914,7 +1916,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
|
var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
var settings = new XmlWriterSettings
|
var settings = new XmlWriterSettings
|
||||||
{
|
{
|
||||||
|
@ -1927,7 +1930,8 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
|
|
||||||
var isSeriesEpisode = timer.IsProgramSeries;
|
var isSeriesEpisode = timer.IsProgramSeries;
|
||||||
|
|
||||||
await using (var writer = XmlWriter.Create(stream, settings))
|
var writer = XmlWriter.Create(stream, settings);
|
||||||
|
await using (writer.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
await writer.WriteStartDocumentAsync(true).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -1965,7 +1969,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await writer.WriteStartElementAsync(null, "movie", null);
|
await writer.WriteStartElementAsync(null, "movie", null).ConfigureAwait(false);
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(item.Name))
|
if (!string.IsNullOrWhiteSpace(item.Name))
|
||||||
{
|
{
|
||||||
|
|
|
@ -106,8 +106,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
options.Content = JsonContent.Create(requestList, options: _jsonOptions);
|
options.Content = JsonContent.Create(requestList, options: _jsonOptions);
|
||||||
options.Headers.TryAddWithoutValidation("token", token);
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var dailySchedules = await response.Content.ReadFromJsonAsync<IReadOnlyList<DayDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
var dailySchedules = await JsonSerializer.DeserializeAsync<IReadOnlyList<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (dailySchedules is null)
|
if (dailySchedules is null)
|
||||||
{
|
{
|
||||||
return Array.Empty<ProgramInfo>();
|
return Array.Empty<ProgramInfo>();
|
||||||
|
@ -122,8 +121,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
|
programRequestOptions.Content = JsonContent.Create(programIds, options: _jsonOptions);
|
||||||
|
|
||||||
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
|
using var innerResponse = await Send(programRequestOptions, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var programDetails = await innerResponse.Content.ReadFromJsonAsync<IReadOnlyList<ProgramDetailsDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
var programDetails = await JsonSerializer.DeserializeAsync<IReadOnlyList<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (programDetails is null)
|
if (programDetails is null)
|
||||||
{
|
{
|
||||||
return Array.Empty<ProgramInfo>();
|
return Array.Empty<ProgramInfo>();
|
||||||
|
@ -482,8 +480,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
|
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
return await innerResponse2.Content.ReadFromJsonAsync<IReadOnlyList<ShowImagesDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
return await JsonSerializer.DeserializeAsync<IReadOnlyList<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -510,10 +507,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
|
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
|
||||||
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var root = await httpResponse.Content.ReadFromJsonAsync<IReadOnlyList<HeadendsDto>>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var root = await JsonSerializer.DeserializeAsync<IReadOnlyList<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (root is not null)
|
if (root is not null)
|
||||||
{
|
{
|
||||||
foreach (HeadendsDto headend in root)
|
foreach (HeadendsDto headend in root)
|
||||||
|
@ -649,8 +643,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
|
|
||||||
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var root = await response.Content.ReadFromJsonAsync<TokenDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (string.Equals(root?.Message, "OK", StringComparison.Ordinal))
|
if (string.Equals(root?.Message, "OK", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
|
_logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
|
||||||
|
@ -691,10 +684,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
using var httpResponse = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
|
||||||
httpResponse.EnsureSuccessStatusCode();
|
httpResponse.EnsureSuccessStatusCode();
|
||||||
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var root = await httpResponse.Content.ReadFromJsonAsync<LineupsDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
using var response = httpResponse.Content;
|
|
||||||
var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false;
|
return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false;
|
||||||
}
|
}
|
||||||
catch (HttpRequestException ex)
|
catch (HttpRequestException ex)
|
||||||
|
@ -748,8 +738,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
options.Headers.TryAddWithoutValidation("token", token);
|
options.Headers.TryAddWithoutValidation("token", token);
|
||||||
|
|
||||||
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
|
||||||
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var root = await httpResponse.Content.ReadFromJsonAsync<ChannelDto>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
var root = await JsonSerializer.DeserializeAsync<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
|
|
||||||
if (root is null)
|
if (root is null)
|
||||||
{
|
{
|
||||||
return new List<ChannelInfo>();
|
return new List<ChannelInfo>();
|
||||||
|
|
|
@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||||
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
|
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
|
if (internalQuery.OrderBy.All(i => i.OrderBy != ItemSortBy.SortName))
|
||||||
{
|
{
|
||||||
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||||
|
|
|
@ -9,6 +9,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
@ -27,7 +28,6 @@ using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using MediaBrowser.Model.Net;
|
using MediaBrowser.Model.Net;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
|
@ -76,13 +76,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var lineup = await response.Content.ReadFromJsonAsync<IEnumerable<Channels>>(_jsonOptions, cancellationToken).ConfigureAwait(false) ?? Enumerable.Empty<Channels>();
|
||||||
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
|
|
||||||
.ConfigureAwait(false) ?? new List<Channels>();
|
|
||||||
|
|
||||||
if (info.ImportFavoritesOnly)
|
if (info.ImportFavoritesOnly)
|
||||||
{
|
{
|
||||||
lineup = lineup.Where(i => i.Favorite).ToList();
|
lineup = lineup.Where(i => i.Favorite);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lineup.Where(i => !i.DRM).ToList();
|
return lineup.Where(i => !i.DRM).ToList();
|
||||||
|
@ -129,9 +126,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
.GetAsync(GetApiUrl(info) + "/discover.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
.GetAsync(GetApiUrl(info) + "/discover.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
response.EnsureSuccessStatusCode();
|
response.EnsureSuccessStatusCode();
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
var discoverResponse = await response.Content.ReadFromJsonAsync<DiscoverResponse>(_jsonOptions, cancellationToken).ConfigureAwait(false);
|
||||||
var discoverResponse = await JsonSerializer.DeserializeAsync<DiscoverResponse>(stream, _jsonOptions, cancellationToken)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cacheKey))
|
if (!string.IsNullOrEmpty(cacheKey))
|
||||||
{
|
{
|
||||||
|
@ -175,34 +170,37 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default)
|
||||||
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
.GetAsync(string.Format(CultureInfo.InvariantCulture, "{0}/tuners.html", GetApiUrl(info)), HttpCompletionOption.ResponseHeadersRead, cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
|
|
||||||
var tuners = new List<LiveTvTunerInfo>();
|
var tuners = new List<LiveTvTunerInfo>();
|
||||||
await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
|
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
await using (stream.ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
string stripedLine = StripXML(line);
|
using var sr = new StreamReader(stream, System.Text.Encoding.UTF8);
|
||||||
if (stripedLine.Contains("Channel", StringComparison.Ordinal))
|
await foreach (var line in sr.ReadAllLinesAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
LiveTvTunerStatus status;
|
string stripedLine = StripXML(line);
|
||||||
var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
if (stripedLine.Contains("Channel", StringComparison.Ordinal))
|
||||||
var name = stripedLine.Substring(0, index - 1);
|
|
||||||
var currentChannel = stripedLine.Substring(index + 7);
|
|
||||||
if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
|
|
||||||
{
|
{
|
||||||
status = LiveTvTunerStatus.LiveTv;
|
LiveTvTunerStatus status;
|
||||||
}
|
var index = stripedLine.IndexOf("Channel", StringComparison.OrdinalIgnoreCase);
|
||||||
else
|
var name = stripedLine.Substring(0, index - 1);
|
||||||
{
|
var currentChannel = stripedLine.Substring(index + 7);
|
||||||
status = LiveTvTunerStatus.Available;
|
if (string.Equals(currentChannel, "none", StringComparison.Ordinal))
|
||||||
}
|
{
|
||||||
|
status = LiveTvTunerStatus.LiveTv;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status = LiveTvTunerStatus.Available;
|
||||||
|
}
|
||||||
|
|
||||||
tuners.Add(new LiveTvTunerInfo
|
tuners.Add(new LiveTvTunerInfo
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
SourceType = string.IsNullOrWhiteSpace(model.ModelNumber) ? Name : model.ModelNumber,
|
||||||
ProgramName = currentChannel,
|
ProgramName = currentChannel,
|
||||||
Status = status
|
Status = status
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,8 +44,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||||
StopStreaming(socket).GetAwaiter().GetResult();
|
StopStreaming(socket).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CheckTunerAvailability(IPAddress remoteIP, int tuner, CancellationToken cancellationToken)
|
public async Task<bool> CheckTunerAvailability(IPAddress remoteIP, int tuner, CancellationToken cancellationToken)
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -22,7 +21,6 @@ using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.MediaInfo;
|
using MediaBrowser.Model.MediaInfo;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Net.Http.Headers;
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
|
|
@ -123,5 +123,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.",
|
"TaskKeyframeExtractorDescription": "Haal keyframes vanuit video lêers om meer presiese HLS afspeellyste te maak. Dit kan lank duur.",
|
||||||
"TaskKeyframeExtractor": "Keyframe Ekstraktor",
|
"TaskKeyframeExtractor": "Keyframe Ekstraktor",
|
||||||
"External": "Ekstern",
|
"External": "Ekstern",
|
||||||
"HearingImpaired": "gehoorgestremd"
|
"HearingImpaired": "gehoorgestremd",
|
||||||
|
"TaskRefreshTrickplayImages": "Genereer Fopspeel Beelde",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Skep fopspeel voorskou vir videos in aangeskakelde media versameling."
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,7 @@
|
||||||
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
"TaskCleanTranscodeDescription": "Выдаляе перакадзіраваныя файлы, старэйшыя за адзін дзень.",
|
||||||
"TaskRefreshChannels": "Абнавіць каналы",
|
"TaskRefreshChannels": "Абнавіць каналы",
|
||||||
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
|
"TaskDownloadMissingSubtitles": "Спампаваць адсутныя субтытры",
|
||||||
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу."
|
"TaskKeyframeExtractorDescription": "Выдае ключавыя кадры з відэафайлаў для стварэння больш дакладных спісаў прайгравання HLS. Гэта задача можа працаваць у працягу доўгага часу.",
|
||||||
|
"TaskRefreshTrickplayImages": "Стварыце выявы Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Стварае прагляд відэаролікаў для Trickplay у падключаных бібліятэках."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,6 @@
|
||||||
"TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен ХЛС списък . Задачата може да отнеме много време.",
|
"TaskKeyframeExtractorDescription": "Извличат се ключови кадри от видеофайловете ,за да се създаде по точен ХЛС списък . Задачата може да отнеме много време.",
|
||||||
"TaskKeyframeExtractor": "Извличане на ключови кадри",
|
"TaskKeyframeExtractor": "Извличане на ключови кадри",
|
||||||
"External": "Външен",
|
"External": "Външен",
|
||||||
"HearingImpaired": "Увреден слух"
|
"HearingImpaired": "Увреден слух",
|
||||||
|
"TaskRefreshTrickplayImages": "Генерирай изображение"
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
"TaskKeyframeExtractorDescription": "Vytahuje klíčové snímky ze souborů videa za účelem vytváření přesnějších seznamů přehrávání HLS. Tento úkol může trvat velmi dlouho.",
|
||||||
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
"TaskKeyframeExtractor": "Vytahovač klíčových snímků",
|
||||||
"External": "Externí",
|
"External": "Externí",
|
||||||
"HearingImpaired": "Sluchově postižení"
|
"HearingImpaired": "Sluchově postižení",
|
||||||
|
"TaskRefreshTrickplayImages": "Generovat obrázky pro Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Obrázky Trickplay se používají k zobrazení náhledů u videí v knihovnách, kde je to povoleno."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
|
"TaskKeyframeExtractorDescription": "Extrahiere Keyframes aus Videodateien, um präzisere HLS-Playlisten zu erzeugen. Dieser Vorgang kann sehr lange dauern.",
|
||||||
"TaskKeyframeExtractor": "Keyframe Extraktor",
|
"TaskKeyframeExtractor": "Keyframe Extraktor",
|
||||||
"External": "Extern",
|
"External": "Extern",
|
||||||
"HearingImpaired": "Hörgeschädigt"
|
"HearingImpaired": "Hörgeschädigt",
|
||||||
|
"TaskRefreshTrickplayImages": "Trickplay-Bilder generieren",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Erstellt eine Trickplay-Vorschau für Videos in aktivierten Bibliotheken."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.",
|
"TaskKeyframeExtractorDescription": "Εξάγει καρέ από αρχεία βίντεο για να δημιουργήσει πιο ακριβείς λίστες αναπαραγωγής HLS. Αυτή η διεργασία μπορεί να πάρει χρόνο.",
|
||||||
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
"TaskKeyframeExtractor": "Εξαγωγέας βασικών καρέ βίντεο",
|
||||||
"External": "Εξωτερικό",
|
"External": "Εξωτερικό",
|
||||||
"HearingImpaired": "Με προβλήματα ακοής"
|
"HearingImpaired": "Με προβλήματα ακοής",
|
||||||
|
"TaskRefreshTrickplayImages": "Δημιουργήστε εικόνες Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Δημιουργεί προεπισκοπήσεις trickplay για βίντεο σε ενεργοποιημένες βιβλιοθήκες."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
"TaskKeyframeExtractorDescription": "Extracts keyframes from video files to create more precise HLS playlists. This task may run for a long time.",
|
||||||
"TaskKeyframeExtractor": "Keyframe Extractor",
|
"TaskKeyframeExtractor": "Keyframe Extractor",
|
||||||
"External": "External",
|
"External": "External",
|
||||||
"HearingImpaired": "Hearing Impaired"
|
"HearingImpaired": "Hearing Impaired",
|
||||||
|
"TaskRefreshTrickplayImages": "Generate Trickplay Images",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries."
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,8 @@
|
||||||
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
||||||
"TaskRefreshPeople": "Refresh People",
|
"TaskRefreshPeople": "Refresh People",
|
||||||
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
||||||
|
"TaskRefreshTrickplayImages": "Generate Trickplay Images",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Creates trickplay previews for videos in enabled libraries.",
|
||||||
"TaskUpdatePlugins": "Update Plugins",
|
"TaskUpdatePlugins": "Update Plugins",
|
||||||
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
||||||
"TaskCleanTranscode": "Clean Transcode Directory",
|
"TaskCleanTranscode": "Clean Transcode Directory",
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.",
|
"TaskKeyframeExtractorDescription": "Extrae los fotogramas clave de los archivos de vídeo para crear listas HLS más precisas. Esta tarea puede tardar mucho tiempo.",
|
||||||
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
"TaskKeyframeExtractor": "Extractor de Fotogramas Clave",
|
||||||
"External": "Externo",
|
"External": "Externo",
|
||||||
"HearingImpaired": "Discapacidad Auditiva"
|
"HearingImpaired": "Discapacidad Auditiva",
|
||||||
|
"TaskRefreshTrickplayImages": "Generar miniaturas de línea de tiempo",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Crear miniaturas de tiempo para videos en las librerías habilitadas."
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
"TaskKeyframeExtractorDescription": "Purkaa videotiedostojen avainkuvat tarkempien HLS-toistolistojen luomiseksi. Tehtävä saattaa kestää huomattavan pitkään.",
|
||||||
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
"TaskKeyframeExtractor": "Avainkuvien purkain",
|
||||||
"External": "Ulkoinen",
|
"External": "Ulkoinen",
|
||||||
"HearingImpaired": "Kuulorajoitteinen"
|
"HearingImpaired": "Kuulorajoitteinen",
|
||||||
|
"TaskRefreshTrickplayImages": "Luo Trickplay-kuvat",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Luo Trickplay-esikatselut käytössä olevien kirjastojen videoista."
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,6 @@
|
||||||
"HearingImpaired": "Bingi",
|
"HearingImpaired": "Bingi",
|
||||||
"TaskKeyframeExtractor": "Tagabunot ng Keyframe",
|
"TaskKeyframeExtractor": "Tagabunot ng Keyframe",
|
||||||
"TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.",
|
"TaskKeyframeExtractorDescription": "Nagbubunot ng keyframe mula sa mga bidyo upang makabuo ng mas tumpak na HLS playlist. Maaaring matagal ito gawin.",
|
||||||
"External": "External"
|
"External": "External",
|
||||||
|
"TaskRefreshTrickplayImages": "Gumawa ng Trickplay na Imahe"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"Artists": "Listafólk",
|
||||||
|
"Collections": "Søvn",
|
||||||
|
"Default": "Sjálvgildi",
|
||||||
|
"DeviceOfflineWithName": "{0} hevur slitið sambandið",
|
||||||
|
"External": "Ytri",
|
||||||
|
"Genres": "Greinar",
|
||||||
|
"Albums": "Album",
|
||||||
|
"AppDeviceValues": "App: {0}, Eind: {1}",
|
||||||
|
"Application": "Nýtsluskipan",
|
||||||
|
"Books": "Bøkur",
|
||||||
|
"Channels": "Rásir",
|
||||||
|
"ChapterNameValue": "Kapittul {0}",
|
||||||
|
"DeviceOnlineWithName": "{0} er sambundið",
|
||||||
|
"Favorites": "Yndis",
|
||||||
|
"Folders": "Mappur",
|
||||||
|
"Forced": "Kravt"
|
||||||
|
}
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||||
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
||||||
"External": "Externe",
|
"External": "Externe",
|
||||||
"HearingImpaired": "Malentendants"
|
"HearingImpaired": "Malentendants",
|
||||||
|
"TaskRefreshTrickplayImages": "Générer des images Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées."
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"Artists": "Artistes",
|
"Artists": "Artistes",
|
||||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||||
"Books": "Livres",
|
"Books": "Livres",
|
||||||
"CameraImageUploadedFrom": "Une photo a été téléversée depuis {0}",
|
"CameraImageUploadedFrom": "Une photo a été téléchargée depuis {0}",
|
||||||
"Channels": "Chaînes",
|
"Channels": "Chaînes",
|
||||||
"ChapterNameValue": "Chapitre {0}",
|
"ChapterNameValue": "Chapitre {0}",
|
||||||
"Collections": "Collections",
|
"Collections": "Collections",
|
||||||
|
@ -16,14 +16,14 @@
|
||||||
"Folders": "Dossiers",
|
"Folders": "Dossiers",
|
||||||
"Genres": "Genres",
|
"Genres": "Genres",
|
||||||
"HeaderAlbumArtists": "Artistes de l'album",
|
"HeaderAlbumArtists": "Artistes de l'album",
|
||||||
"HeaderContinueWatching": "Reprendre le visionnage",
|
"HeaderContinueWatching": "Continuer de regarder",
|
||||||
"HeaderFavoriteAlbums": "Albums favoris",
|
"HeaderFavoriteAlbums": "Albums favoris",
|
||||||
"HeaderFavoriteArtists": "Artistes préférés",
|
"HeaderFavoriteArtists": "Artistes préférés",
|
||||||
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
"HeaderFavoriteEpisodes": "Épisodes favoris",
|
||||||
"HeaderFavoriteShows": "Séries favorites",
|
"HeaderFavoriteShows": "Séries favorites",
|
||||||
"HeaderFavoriteSongs": "Chansons préférées",
|
"HeaderFavoriteSongs": "Chansons préférées",
|
||||||
"HeaderLiveTV": "TV en direct",
|
"HeaderLiveTV": "TV en direct",
|
||||||
"HeaderNextUp": "À suivre",
|
"HeaderNextUp": "Prochain à venir",
|
||||||
"HeaderRecordingGroups": "Groupes d'enregistrements",
|
"HeaderRecordingGroups": "Groupes d'enregistrements",
|
||||||
"HomeVideos": "Vidéos personnelles",
|
"HomeVideos": "Vidéos personnelles",
|
||||||
"Inherit": "Hériter",
|
"Inherit": "Hériter",
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
"ScheduledTaskStartedWithName": "{0} a démarré",
|
"ScheduledTaskStartedWithName": "{0} a démarré",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
|
||||||
"Shows": "Séries",
|
"Shows": "Séries",
|
||||||
"Songs": "Titres",
|
"Songs": "Chansons",
|
||||||
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
|
"StartupEmbyServerIsLoading": "Le serveur Jellyfin est en cours de chargement. Veuillez réessayer dans quelques instants.",
|
||||||
"SubtitleDownloadFailureForItem": "Le téléchargement des sous-titres pour {0} a échoué.",
|
"SubtitleDownloadFailureForItem": "Le téléchargement des sous-titres pour {0} a échoué.",
|
||||||
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
|
"SubtitleDownloadFailureFromForItem": "Échec du téléchargement des sous-titres depuis {0} pour {1}",
|
||||||
|
@ -122,7 +122,9 @@
|
||||||
"TaskOptimizeDatabaseDescription": "Réduit les espaces vides ou inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la médiathèque ou toute autre modification de la base de données peut améliorer les performances du serveur.",
|
"TaskOptimizeDatabaseDescription": "Réduit les espaces vides ou inutiles et compacte la base de données. Utiliser cette fonction après une mise à jour de la médiathèque ou toute autre modification de la base de données peut améliorer les performances du serveur.",
|
||||||
"TaskOptimizeDatabase": "Optimiser la base de données",
|
"TaskOptimizeDatabase": "Optimiser la base de données",
|
||||||
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
"TaskKeyframeExtractorDescription": "Extrait les images clés des fichiers vidéo pour créer des listes de lecture HLS plus précises. Cette tâche peut durer très longtemps.",
|
||||||
"TaskKeyframeExtractor": "Extracteur d'image clé",
|
"TaskKeyframeExtractor": "Extracteur d'images clés",
|
||||||
"External": "Externe",
|
"External": "Externe",
|
||||||
"HearingImpaired": "Malentendants"
|
"HearingImpaired": "Malentendants",
|
||||||
|
"TaskRefreshTrickplayImages": "Générer des images Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Crée des aperçus Trickplay pour les vidéos dans les médiathèques activées."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
|
"TaskKeyframeExtractorDescription": "חלץ תמונות מפתח מקבצי וידאו בכדי ליצור רשימות השמעה מדויקות יותר של HLS. משימה זו עלולה להימשך זמן רב.",
|
||||||
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
|
"TaskKeyframeExtractor": "מחלץ תמונות מפתח",
|
||||||
"External": "חיצוני",
|
"External": "חיצוני",
|
||||||
"HearingImpaired": "לקוי שמיעה"
|
"HearingImpaired": "לקוי שמיעה",
|
||||||
|
"TaskRefreshTrickplayImages": "יצירת תמונות המחשה",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "יוצר תמונות המחשה לסרטונים שפעילים בספריות."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.",
|
"TaskKeyframeExtractorDescription": "Izvlačenje ključnih okvira iz videozapisa za stvaranje objektivnije HLS liste za reprodukciju. Pokretanje ovog zadatka može potrajati.",
|
||||||
"TaskKeyframeExtractor": "Izvoditelj ključnog okvira",
|
"TaskKeyframeExtractor": "Izvoditelj ključnog okvira",
|
||||||
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.",
|
"TaskOptimizeDatabaseDescription": "Sažima bazu podataka i uklanja prazan prostor. Pokretanje ovog zadatka, može poboljšati performanse nakon provođenja indeksiranja biblioteke ili provođenja drugih promjena koje utječu na bazu podataka.",
|
||||||
"HearingImpaired": "Oštećen sluh"
|
"HearingImpaired": "Oštećen sluh",
|
||||||
|
"TaskRefreshTrickplayImages": "Generiraj Trickplay Slike",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Kreira trickplay pretpreglede za videe u omogućenim knjižnicama."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractor": "Kulcsképkockák kibontása",
|
"TaskKeyframeExtractor": "Kulcsképkockák kibontása",
|
||||||
"TaskKeyframeExtractorDescription": "Kibontja a kulcsképkockákat a videófájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
"TaskKeyframeExtractorDescription": "Kibontja a kulcsképkockákat a videófájlokból, hogy pontosabb HLS lejátszási listákat hozzon létre. Ez a feladat hosszú ideig tarthat.",
|
||||||
"External": "Külső",
|
"External": "Külső",
|
||||||
"HearingImpaired": "Hallássérült"
|
"HearingImpaired": "Hallássérült",
|
||||||
|
"TaskRefreshTrickplayImages": "Trickplay képek generálása",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz."
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
"HeaderFavoriteArtists": "Uppáhalds Listamenn",
|
"HeaderFavoriteArtists": "Uppáhalds Listamenn",
|
||||||
"HeaderFavoriteAlbums": "Uppáhalds Plötur",
|
"HeaderFavoriteAlbums": "Uppáhalds Plötur",
|
||||||
"HeaderContinueWatching": "Halda áfram að horfa",
|
"HeaderContinueWatching": "Halda áfram að horfa",
|
||||||
"HeaderAlbumArtists": "Höfundur plötu",
|
"HeaderAlbumArtists": "Listamaður á umslagi",
|
||||||
"Genres": "Tegundir",
|
"Genres": "Stefnur",
|
||||||
"Folders": "Möppur",
|
"Folders": "Möppur",
|
||||||
"Favorites": "Uppáhalds",
|
"Favorites": "Uppáhalds",
|
||||||
"FailedLoginAttemptWithUserName": "{0} reyndi að auðkenna sig",
|
"FailedLoginAttemptWithUserName": "{0} reyndi að auðkenna sig",
|
||||||
|
@ -22,32 +22,32 @@
|
||||||
"DeviceOfflineWithName": "{0} hefur aftengst",
|
"DeviceOfflineWithName": "{0} hefur aftengst",
|
||||||
"Collections": "Söfn",
|
"Collections": "Söfn",
|
||||||
"ChapterNameValue": "Kafli {0}",
|
"ChapterNameValue": "Kafli {0}",
|
||||||
"Channels": "Stöðvar",
|
"Channels": "Rásir",
|
||||||
"CameraImageUploadedFrom": "Ný ljósmynd frá myndavél hefur verið hlaðið upp frá {0}",
|
"CameraImageUploadedFrom": "{0} hefur hlaðið upp nýrri ljósmynd úr myndavél sinni",
|
||||||
"Books": "Bækur",
|
"Books": "Bækur",
|
||||||
"AuthenticationSucceededWithUserName": "{0} auðkenning tókst",
|
"AuthenticationSucceededWithUserName": "Auðkenning fyrir {0} tókst",
|
||||||
"Artists": "Listamaður",
|
"Artists": "Listamenn",
|
||||||
"Application": "Forrit",
|
"Application": "Forrit",
|
||||||
"AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}",
|
"AppDeviceValues": "Snjallforrit: {0}, Tæki: {1}",
|
||||||
"Albums": "Plötur",
|
"Albums": "Plötur",
|
||||||
"Plugin": "Viðbót",
|
"Plugin": "Viðbótarvirkni",
|
||||||
"Photos": "Myndir",
|
"Photos": "Ljósmyndir",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Myndbandafspilun stöðvuð",
|
"NotificationOptionVideoPlaybackStopped": "Myndbandsafspilun stöðvuð",
|
||||||
"NotificationOptionVideoPlayback": "Myndbandafspilun hafin",
|
"NotificationOptionVideoPlayback": "Myndbandsafspilun hafin",
|
||||||
"NotificationOptionUserLockedOut": "Notandi læstur úti",
|
"NotificationOptionUserLockedOut": "Notandi læstur úti",
|
||||||
"NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynileg",
|
"NotificationOptionServerRestartRequired": "Endurræsing þjóns er nauðsynleg",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Viðbótar uppfærsla uppsett",
|
"NotificationOptionPluginUpdateInstalled": "Uppfærslu á viðbótarvirkni lokið",
|
||||||
"NotificationOptionPluginUninstalled": "Viðbót fjarlægð",
|
"NotificationOptionPluginUninstalled": "Viðbótarvirkni fjarlægð",
|
||||||
"NotificationOptionPluginInstalled": "Viðbót sett upp",
|
"NotificationOptionPluginInstalled": "Viðbótarvirkni sett upp",
|
||||||
"NotificationOptionPluginError": "Bilun í viðbót",
|
"NotificationOptionPluginError": "Bilun í viðbót",
|
||||||
"NotificationOptionInstallationFailed": "Uppsetning tókst ekki",
|
"NotificationOptionInstallationFailed": "Uppsetning tókst ekki",
|
||||||
"NotificationOptionCameraImageUploaded": "Myndavélarmynd hlaðið upp",
|
"NotificationOptionCameraImageUploaded": "Ljósmynd hlaðið upp",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Hljóðafspilun stöðvuð",
|
"NotificationOptionAudioPlaybackStopped": "Hljóðafspilun stöðvuð",
|
||||||
"NotificationOptionAudioPlayback": "Hljóðafspilun hafin",
|
"NotificationOptionAudioPlayback": "Hljóðafspilun hafin",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Uppfærsla uppsett",
|
"NotificationOptionApplicationUpdateInstalled": "Uppfærsla uppsett",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Uppfærsla í boði",
|
"NotificationOptionApplicationUpdateAvailable": "Uppfærsla í boði",
|
||||||
"NameSeasonUnknown": "Sería óþekkt",
|
"NameSeasonUnknown": "Þáttaröð óþekkt",
|
||||||
"NameSeasonNumber": "Sería {0}",
|
"NameSeasonNumber": "Þáttaröð {0}",
|
||||||
"MixedContent": "Blandað efni",
|
"MixedContent": "Blandað efni",
|
||||||
"MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar",
|
"MessageServerConfigurationUpdated": "Stillingar þjóns hafa verið uppfærðar",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin þjónn hefur verið uppfærður í {0}",
|
||||||
|
@ -57,24 +57,24 @@
|
||||||
"User": "Notandi",
|
"User": "Notandi",
|
||||||
"System": "Kerfi",
|
"System": "Kerfi",
|
||||||
"NotificationOptionNewLibraryContent": "Nýju efni bætt við",
|
"NotificationOptionNewLibraryContent": "Nýju efni bætt við",
|
||||||
"NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er fáanleg til niðurhals.",
|
"NewVersionIsAvailable": "Ný útgáfa af Jellyfin þjón er tilbúin til niðurhals.",
|
||||||
"NameInstallFailed": "{0} uppsetning mistókst",
|
"NameInstallFailed": "{0} uppsetning mistókst",
|
||||||
"MusicVideos": "Tónlistarmyndbönd",
|
"MusicVideos": "Tónlistarmyndbönd",
|
||||||
"Music": "Tónlist",
|
"Music": "Tónlist",
|
||||||
"Movies": "Kvikmyndir",
|
"Movies": "Kvikmyndir",
|
||||||
"UserDeletedWithName": "Notanda {0} hefur verið eytt",
|
"UserDeletedWithName": "Notanda {0} hefur verið eytt",
|
||||||
"UserCreatedWithName": "Notandi {0} hefur verið stofnaður",
|
"UserCreatedWithName": "Notandi {0} hefur verið stofnaður",
|
||||||
"TvShows": "Þættir",
|
"TvShows": "Sjónvarpsþættir",
|
||||||
"Sync": "Samstilla",
|
"Sync": "Samstilla",
|
||||||
"Songs": "Lög",
|
"Songs": "Lög",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} þarf að endurræsa",
|
"ServerNameNeedsToBeRestarted": "{0} þarf að vera endurræstur",
|
||||||
"ScheduledTaskStartedWithName": "{0} hafin",
|
"ScheduledTaskStartedWithName": "{0} hafin",
|
||||||
"ScheduledTaskFailedWithName": "{0} mistókst",
|
"ScheduledTaskFailedWithName": "{0} mistókst",
|
||||||
"PluginUpdatedWithName": "{0} var uppfært",
|
"PluginUpdatedWithName": "{0} var uppfært",
|
||||||
"PluginUninstalledWithName": "{0} var fjarlægt",
|
"PluginUninstalledWithName": "{0} var fjarlægt",
|
||||||
"PluginInstalledWithName": "{0} var sett upp",
|
"PluginInstalledWithName": "{0} var sett upp",
|
||||||
"NotificationOptionTaskFailed": "Tímasett verkefni mistókst",
|
"NotificationOptionTaskFailed": "Tímasett verkefni mistókst",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að hlaðast. Vinsamlega prufaðu aftur fljótlega.",
|
"StartupEmbyServerIsLoading": "Jellyfin netþjónnin er að ræsa sig upp. Vinsamlegast reyndu aftur fljótlega.",
|
||||||
"VersionNumber": "Útgáfa {0}",
|
"VersionNumber": "Útgáfa {0}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt",
|
"ValueHasBeenAddedToLibrary": "{0} hefur verið bætt við í gagnasafnið þitt",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}",
|
"UserStoppedPlayingItemWithValues": "{0} hefur lokið spilunar af {1} á {2}",
|
||||||
|
@ -83,14 +83,14 @@
|
||||||
"UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt",
|
"UserPasswordChangedWithName": "Lykilorði fyrir notandann {0} hefur verið breytt",
|
||||||
"UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}",
|
"UserOnlineFromDevice": "{0} hefur verið virkur síðan {1}",
|
||||||
"UserOfflineFromDevice": "{0} hefur aftengst frá {1}",
|
"UserOfflineFromDevice": "{0} hefur aftengst frá {1}",
|
||||||
"UserLockedOutWithName": "Notanda {0} hefur verið heflaður aðgangur",
|
"UserLockedOutWithName": "Notandi {0} hefur verið læstur úti",
|
||||||
"UserDownloadingItemWithValues": "{0} Hleður niður {1}",
|
"UserDownloadingItemWithValues": "{0} hleður niður {1}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}",
|
"SubtitleDownloadFailureFromForItem": "Tókst ekki að hala niður skjátextum frá {0} til {1}",
|
||||||
"ProviderValue": "Veitandi: {0}",
|
"ProviderValue": "Efnisveita: {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Stilling {0} hefur verið uppfærð á netþjón",
|
||||||
"ValueSpecialEpisodeName": "Sérstakt - {0}",
|
"ValueSpecialEpisodeName": "Sérstaktur - {0}",
|
||||||
"Shows": "Sýningar",
|
"Shows": "Þættir",
|
||||||
"Playlists": "Spilunarlisti",
|
"Playlists": "Efnisskrár",
|
||||||
"TaskRefreshChannelsDescription": "Endurhlaða upplýsingum netrása.",
|
"TaskRefreshChannelsDescription": "Endurhlaða upplýsingum netrása.",
|
||||||
"TaskRefreshChannels": "Endurhlaða Rásir",
|
"TaskRefreshChannels": "Endurhlaða Rásir",
|
||||||
"TaskCleanTranscodeDescription": "Eyða umkóðuðum skrám sem eru meira en einum degi eldri.",
|
"TaskCleanTranscodeDescription": "Eyða umkóðuðum skrám sem eru meira en einum degi eldri.",
|
||||||
|
@ -116,5 +116,12 @@
|
||||||
"TaskCleanLogsDescription": "Eyðir færslu skrám sem eru meira en {0} gömul.",
|
"TaskCleanLogsDescription": "Eyðir færslu skrám sem eru meira en {0} gömul.",
|
||||||
"TaskCleanLogs": "Hreinsa færslu skrá",
|
"TaskCleanLogs": "Hreinsa færslu skrá",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Leitar á netinu að texta sem vantar miðað við uppsetningu lýsigagna.",
|
"TaskDownloadMissingSubtitlesDescription": "Leitar á netinu að texta sem vantar miðað við uppsetningu lýsigagna.",
|
||||||
"HearingImpaired": "Heyrnarskertur"
|
"HearingImpaired": "Heyrnarskertur",
|
||||||
|
"TaskOptimizeDatabaseDescription": "Þjappar gagnagrunni og bætir við lausu diskaplássi. Að keyra þessa aðgerð eftir skönnun safnsins, eða eftir einhverjar breytingar sem fela í sér gagnagrunnsbreytingar, gætu aukið hraðvirkni.",
|
||||||
|
"TaskKeyframeExtractor": "Lykilrammaplokkari",
|
||||||
|
"TaskKeyframeExtractorDescription": "Plokkar lykilramma úr myndbandsskrám til að búa til nákvæmari HLS uppskiptingarlista. Þetta verk getur tekið langan tíma.",
|
||||||
|
"TaskRefreshChapterImages": "Plokka kafla-myndir",
|
||||||
|
"TaskCleanActivityLogDescription": "Eyðir virkniskráningarfærslum sem hafa náð settum hámarksaldri.",
|
||||||
|
"Forced": "Þvingað",
|
||||||
|
"External": "Útvær"
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
"TaskKeyframeExtractor": "Estrattore di Keyframe",
|
||||||
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
|
"TaskKeyframeExtractorDescription": "Estrae i keyframe dai video per creare migliori playlist HLS. Questa procedura potrebbe richiedere molto tempo.",
|
||||||
"External": "Esterno",
|
"External": "Esterno",
|
||||||
"HearingImpaired": "con problemi di udito"
|
"HearingImpaired": "con problemi di udito",
|
||||||
|
"TaskRefreshTrickplayImages": "Genera immagini Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Crea anteprime trickplay per i video nelle librerie abilitate."
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,19 @@
|
||||||
"Application": "アプリケーション",
|
"Application": "アプリケーション",
|
||||||
"Artists": "アーティスト",
|
"Artists": "アーティスト",
|
||||||
"AuthenticationSucceededWithUserName": "{0} 認証に成功しました",
|
"AuthenticationSucceededWithUserName": "{0} 認証に成功しました",
|
||||||
"Books": "ブックス",
|
"Books": "ブック",
|
||||||
"CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました",
|
"CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました",
|
||||||
"Channels": "チャンネル",
|
"Channels": "チャンネル",
|
||||||
"ChapterNameValue": "チャプター {0}",
|
"ChapterNameValue": "チャプター {0}",
|
||||||
"Collections": "コレクション",
|
"Collections": "コレクション",
|
||||||
"DeviceOfflineWithName": "{0} が切断されました",
|
"DeviceOfflineWithName": "{0} が切断しました",
|
||||||
"DeviceOnlineWithName": "{0} が接続されました",
|
"DeviceOnlineWithName": "{0} が接続しました",
|
||||||
"FailedLoginAttemptWithUserName": "ログインを試行しましたが {0} によって失敗しました",
|
"FailedLoginAttemptWithUserName": "{0} からのログインに失敗しました",
|
||||||
"Favorites": "お気に入り",
|
"Favorites": "お気に入り",
|
||||||
"Folders": "フォルダー",
|
"Folders": "フォルダー",
|
||||||
"Genres": "ジャンル",
|
"Genres": "ジャンル",
|
||||||
"HeaderAlbumArtists": "アルバムアーティスト",
|
"HeaderAlbumArtists": "アルバムアーティスト",
|
||||||
"HeaderContinueWatching": "続けて見る",
|
"HeaderContinueWatching": "再生を続ける",
|
||||||
"HeaderFavoriteAlbums": "お気に入りのアルバム",
|
"HeaderFavoriteAlbums": "お気に入りのアルバム",
|
||||||
"HeaderFavoriteArtists": "お気に入りのアーティスト",
|
"HeaderFavoriteArtists": "お気に入りのアーティスト",
|
||||||
"HeaderFavoriteEpisodes": "お気に入りのエピソード",
|
"HeaderFavoriteEpisodes": "お気に入りのエピソード",
|
||||||
|
@ -27,22 +27,22 @@
|
||||||
"HeaderRecordingGroups": "レコーディンググループ",
|
"HeaderRecordingGroups": "レコーディンググループ",
|
||||||
"HomeVideos": "ホームビデオ",
|
"HomeVideos": "ホームビデオ",
|
||||||
"Inherit": "継承",
|
"Inherit": "継承",
|
||||||
"ItemAddedWithName": "{0} をライブラリに追加しました",
|
"ItemAddedWithName": "{0} をライブラリーに追加しました",
|
||||||
"ItemRemovedWithName": "{0} をライブラリから削除しました",
|
"ItemRemovedWithName": "{0} をライブラリーから削除しました",
|
||||||
"LabelIpAddressValue": "IPアドレス: {0}",
|
"LabelIpAddressValue": "IPアドレス: {0}",
|
||||||
"LabelRunningTimeValue": "稼働時間: {0}",
|
"LabelRunningTimeValue": "時間: {0}",
|
||||||
"Latest": "最新",
|
"Latest": "最新",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server が更新されました",
|
"MessageApplicationUpdated": "Jellyfin Server を更新しました",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server が {0}に更新されました",
|
"MessageApplicationUpdatedTo": "Jellyfin Server を {0}に更新しました",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました",
|
"MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} を更新しました",
|
||||||
"MessageServerConfigurationUpdated": "サーバー設定が更新されました",
|
"MessageServerConfigurationUpdated": "サーバー設定を更新しました",
|
||||||
"MixedContent": "ミックスコンテンツ",
|
"MixedContent": "ミックスコンテンツ",
|
||||||
"Movies": "映画",
|
"Movies": "映画",
|
||||||
"Music": "音楽",
|
"Music": "音楽",
|
||||||
"MusicVideos": "ミュージックビデオ",
|
"MusicVideos": "ミュージックビデオ",
|
||||||
"NameInstallFailed": "{0}のインストールに失敗しました",
|
"NameInstallFailed": "{0}のインストールに失敗しました",
|
||||||
"NameSeasonNumber": "シーズン {0}",
|
"NameSeasonNumber": "シーズン {0}",
|
||||||
"NameSeasonUnknown": "不明なシーズン",
|
"NameSeasonUnknown": "シーズン不明",
|
||||||
"NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。",
|
"NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
|
"NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
|
"NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
|
||||||
|
@ -88,18 +88,18 @@
|
||||||
"UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました",
|
"UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました",
|
||||||
"UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています",
|
"UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
|
"UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
|
||||||
"ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました",
|
"ValueHasBeenAddedToLibrary": "{0} をメディアライブラリーに追加しました",
|
||||||
"ValueSpecialEpisodeName": "スペシャル - {0}",
|
"ValueSpecialEpisodeName": "スペシャル - {0}",
|
||||||
"VersionNumber": "バージョン {0}",
|
"VersionNumber": "バージョン {0}",
|
||||||
"TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
|
"TaskCleanLogsDescription": "{0} 日以上前のログを消去します。",
|
||||||
"TaskCleanLogs": "ログの掃除",
|
"TaskCleanLogs": "ログの掃除",
|
||||||
"TaskRefreshLibraryDescription": "メディアライブラリをスキャンして新しいファイルを探し、メタデータを更新します。",
|
"TaskRefreshLibraryDescription": "メディアライブラリーをスキャンして、新しいファイルを探し、メタデータを更新します。",
|
||||||
"TaskRefreshLibrary": "メディアライブラリのスキャン",
|
"TaskRefreshLibrary": "メディアライブラリーをスキャン",
|
||||||
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
|
"TaskCleanCacheDescription": "不要なキャッシュを消去します。",
|
||||||
"TaskCleanCache": "キャッシュを消去",
|
"TaskCleanCache": "キャッシュを消去",
|
||||||
"TasksChannelsCategory": "ネットチャンネル",
|
"TasksChannelsCategory": "ネットチャンネル",
|
||||||
"TasksApplicationCategory": "アプリケーション",
|
"TasksApplicationCategory": "アプリケーション",
|
||||||
"TasksLibraryCategory": "ライブラリ",
|
"TasksLibraryCategory": "ライブラリー",
|
||||||
"TasksMaintenanceCategory": "メンテナンス",
|
"TasksMaintenanceCategory": "メンテナンス",
|
||||||
"TaskRefreshChannelsDescription": "ネットチャンネルの情報を更新する。",
|
"TaskRefreshChannelsDescription": "ネットチャンネルの情報を更新する。",
|
||||||
"TaskRefreshChannels": "チャンネルの更新",
|
"TaskRefreshChannels": "チャンネルの更新",
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
"TaskCleanTranscode": "トランスコードディレクトリの削除",
|
"TaskCleanTranscode": "トランスコードディレクトリの削除",
|
||||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||||
"TaskUpdatePlugins": "プラグインの更新",
|
"TaskUpdatePlugins": "プラグインの更新",
|
||||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータを更新します。",
|
"TaskRefreshPeopleDescription": "メディアライブラリー内の俳優や監督のメタデータを更新します。",
|
||||||
"TaskRefreshPeople": "俳優や監督のデータの更新",
|
"TaskRefreshPeople": "俳優や監督のデータの更新",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索する。",
|
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索する。",
|
||||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||||
|
@ -118,10 +118,12 @@
|
||||||
"Undefined": "未定義",
|
"Undefined": "未定義",
|
||||||
"Forced": "強制",
|
"Forced": "強制",
|
||||||
"Default": "デフォルト",
|
"Default": "デフォルト",
|
||||||
"TaskOptimizeDatabaseDescription": "データベースをコンパクトにして、空き領域を切り詰めます。メディアライブラリのスキャン後でこのタスクを実行するとパフォーマンスが向上する可能性があります。",
|
"TaskOptimizeDatabaseDescription": "データベースをコンパクトにして、空き領域を切り詰めます。メディアライブラリーのスキャンやその他のデータベースの更新を伴う変更の後でこのタスクを実行すると、パフォーマンスが向上します。",
|
||||||
"TaskOptimizeDatabase": "データベースの最適化",
|
"TaskOptimizeDatabase": "データベースの最適化",
|
||||||
"TaskKeyframeExtractorDescription": "より正確なHLSプレイリストを作成するため、動画ファイルからキーフレームを抽出する。この処理には時間がかかる場合があります。",
|
"TaskKeyframeExtractorDescription": "より正確なHLSプレイリストを作成するため、動画ファイルからキーフレームを抽出する。この処理には時間がかかる場合があります。",
|
||||||
"TaskKeyframeExtractor": "キーフレーム抽出",
|
"TaskKeyframeExtractor": "キーフレーム抽出",
|
||||||
"External": "外部",
|
"External": "外部",
|
||||||
"HearingImpaired": "聴覚障害の方"
|
"HearingImpaired": "聴覚障害の方",
|
||||||
|
"TaskRefreshTrickplayImages": "トリックプレー画像を生成",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "有効なライブラリ内のビデオをもとにトリックプレーのプレビューを生成します。"
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,8 @@
|
||||||
"TaskOptimizeDatabase": "Derekqordy oñtailandyru",
|
"TaskOptimizeDatabase": "Derekqordy oñtailandyru",
|
||||||
"TaskKeyframeExtractorDescription": "Naqtyraq HLS oynatu tızımderın jasau üşın beinefaildardan negızgı kadrlardy şyğarady. Būl tapsyrma ūzaq uaqytqa sozyluy mümkın.",
|
"TaskKeyframeExtractorDescription": "Naqtyraq HLS oynatu tızımderın jasau üşın beinefaildardan negızgı kadrlardy şyğarady. Būl tapsyrma ūzaq uaqytqa sozyluy mümkın.",
|
||||||
"TaskKeyframeExtractor": "Negızgı kadrlardy şyğaru",
|
"TaskKeyframeExtractor": "Negızgı kadrlardy şyğaru",
|
||||||
"External": "Syrtqy"
|
"External": "Syrtqy",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Іске қосылған кітапханалардағы бейнелер үшін Trickplay алдын ала түрінде көрсетілімді жасайды.",
|
||||||
|
"TaskRefreshTrickplayImages": "Trickplay үшін суреттерді жасау",
|
||||||
|
"HearingImpaired": "Есту қабілеті нашарға"
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
|
"TaskKeyframeExtractor": "Pagrindinių kadrų ištraukėjas",
|
||||||
"TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
|
"TaskOptimizeDatabaseDescription": "Suspaudžia duomenų bazę ir atlaisvina vietą. Paleidžiant šią užduotį, po bibliotekos skenavimo arba kitų veiksmų kurie galimai modifikuoja duomenų bazė, gali pagerinti greitaveiką.",
|
||||||
"External": "Išorinis",
|
"External": "Išorinis",
|
||||||
"HearingImpaired": "Su klausos sutrikimais"
|
"HearingImpaired": "Su klausos sutrikimais",
|
||||||
|
"TaskRefreshTrickplayImages": "Generuoti Trickplay atvaizdus",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Sukuria trickplay peržiūras vaizdo įrašams įgalintose bibliotekose."
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts",
|
"ServerNameNeedsToBeRestarted": "{0} ir vajadzīgs restarts",
|
||||||
"NotificationOptionTaskFailed": "Plānota uzdevuma kļūme",
|
"NotificationOptionTaskFailed": "Plānota uzdevuma kļūme",
|
||||||
"HeaderRecordingGroups": "Ierakstu Grupas",
|
"HeaderRecordingGroups": "Ierakstu grupas",
|
||||||
"UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}",
|
"UserPolicyUpdatedWithName": "Lietotāju politika atjaunota priekš {0}",
|
||||||
"SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās",
|
"SubtitleDownloadFailureFromForItem": "Subtitru lejupielāde no {0} priekš {1} neizdevās",
|
||||||
"NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta",
|
"NotificationOptionVideoPlaybackStopped": "Video atskaņošana apturēta",
|
||||||
|
@ -14,13 +14,13 @@
|
||||||
"Photos": "Attēli",
|
"Photos": "Attēli",
|
||||||
"NotificationOptionUserLockedOut": "Lietotājs bloķēts",
|
"NotificationOptionUserLockedOut": "Lietotājs bloķēts",
|
||||||
"LabelRunningTimeValue": "Garums: {0}",
|
"LabelRunningTimeValue": "Garums: {0}",
|
||||||
"Inherit": "Mantot",
|
"Inherit": "Pārmantot",
|
||||||
"AppDeviceValues": "Lietotne: {0}, Ierīce: {1}",
|
"AppDeviceValues": "Lietotne: {0}, Ierīce: {1}",
|
||||||
"VersionNumber": "Versija {0}",
|
"VersionNumber": "Versija {0}",
|
||||||
"ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots jūsu multvides bibliotēkai",
|
"ValueHasBeenAddedToLibrary": "{0} ir ticis pievienots jūsu multvides bibliotēkai",
|
||||||
"UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
|
"UserStoppedPlayingItemWithValues": "{0} ir beidzis atskaņot {1} uz {2}",
|
||||||
"UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
|
"UserStartedPlayingItemWithValues": "{0} atskaņo {1} uz {2}",
|
||||||
"UserPasswordChangedWithName": "Parole nomainīta lietotājam {0}",
|
"UserPasswordChangedWithName": "Lietotāja {0} parole tika nomainīta",
|
||||||
"UserOnlineFromDevice": "{0} ir tiešsaistē no {1}",
|
"UserOnlineFromDevice": "{0} ir tiešsaistē no {1}",
|
||||||
"UserOfflineFromDevice": "{0} ir atvienojies no {1}",
|
"UserOfflineFromDevice": "{0} ir atvienojies no {1}",
|
||||||
"UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts",
|
"UserLockedOutWithName": "Lietotājs {0} ir ticis bloķēts",
|
||||||
|
@ -28,23 +28,23 @@
|
||||||
"UserDeletedWithName": "Lietotājs {0} ir izdzēsts",
|
"UserDeletedWithName": "Lietotājs {0} ir izdzēsts",
|
||||||
"UserCreatedWithName": "Lietotājs {0} ir ticis izveidots",
|
"UserCreatedWithName": "Lietotājs {0} ir ticis izveidots",
|
||||||
"User": "Lietotājs",
|
"User": "Lietotājs",
|
||||||
"TvShows": "TV Raidījumi",
|
"TvShows": "TV raidījumi",
|
||||||
"Sync": "Sinhronizācija",
|
"Sync": "Sinhronizācija",
|
||||||
"System": "Sistēma",
|
"System": "Sistēma",
|
||||||
"StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.",
|
"StartupEmbyServerIsLoading": "Jellyfin Serveris lādējas. Lūdzu mēģiniet vēlreiz pēc brīža.",
|
||||||
"Songs": "Dziesmas",
|
"Songs": "Dziesmas",
|
||||||
"Shows": "Raidījumi",
|
"Shows": "Šovi",
|
||||||
"PluginUpdatedWithName": "{0} tika atjaunots",
|
"PluginUpdatedWithName": "{0} tika atjaunots",
|
||||||
"PluginUninstalledWithName": "{0} tika noņemts",
|
"PluginUninstalledWithName": "{0} tika noņemts",
|
||||||
"PluginInstalledWithName": "{0} tika uzstādīts",
|
"PluginInstalledWithName": "{0} tika uzstādīts",
|
||||||
"Plugin": "Paplašinājums",
|
"Plugin": "Paplašinājums",
|
||||||
"Playlists": "Atskaņošanas Saraksti",
|
"Playlists": "Atskaņošanas saraksti",
|
||||||
"MixedContent": "Jaukts saturs",
|
"MixedContent": "Jaukts saturs",
|
||||||
"HomeVideos": "Mājas Video",
|
"HomeVideos": "Mājas video",
|
||||||
"HeaderNextUp": "Nākamais",
|
"HeaderNextUp": "Nākamais",
|
||||||
"ChapterNameValue": "Nodaļa {0}",
|
"ChapterNameValue": "{0}. nodaļa",
|
||||||
"Application": "Lietotne",
|
"Application": "Lietotne",
|
||||||
"NotificationOptionServerRestartRequired": "Vajadzīgs servera restarts",
|
"NotificationOptionServerRestartRequired": "Nepieciešams servera restarts",
|
||||||
"NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts",
|
"NotificationOptionPluginUpdateInstalled": "Paplašinājuma atjauninājums uzstādīts",
|
||||||
"NotificationOptionPluginUninstalled": "Paplašinājums noņemts",
|
"NotificationOptionPluginUninstalled": "Paplašinājums noņemts",
|
||||||
"NotificationOptionPluginInstalled": "Paplašinājums uzstādīts",
|
"NotificationOptionPluginInstalled": "Paplašinājums uzstādīts",
|
||||||
|
@ -56,14 +56,14 @@
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts",
|
"NotificationOptionApplicationUpdateInstalled": "Lietotnes atjauninājums uzstādīts",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams",
|
"NotificationOptionApplicationUpdateAvailable": "Lietotnes atjauninājums pieejams",
|
||||||
"NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.",
|
"NewVersionIsAvailable": "Lejupielādei ir pieejama jauna Jellyfin Server versija.",
|
||||||
"NameSeasonUnknown": "Nezināma Sezona",
|
"NameSeasonUnknown": "Nezināma sezona",
|
||||||
"NameSeasonNumber": "Sezona {0}",
|
"NameSeasonNumber": "{0}. sezona",
|
||||||
"NameInstallFailed": "{0} instalācija neizdevās",
|
"NameInstallFailed": "{0} instalācija neizdevās",
|
||||||
"MusicVideos": "Mūzikas video",
|
"MusicVideos": "Mūzikas video",
|
||||||
"Music": "Mūzika",
|
"Music": "Mūzika",
|
||||||
"Movies": "Filmas",
|
"Movies": "Filmas",
|
||||||
"MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota",
|
"MessageServerConfigurationUpdated": "Servera konfigurācija ir tikusi atjaunota",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} ir tikusi atjaunota",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Servera konfigurācijas sadaļa {0} tika atjaunota",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server ir ticis atjaunots uz {0}",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots",
|
"MessageApplicationUpdated": "Jellyfin Server ir ticis atjaunots",
|
||||||
"Latest": "Jaunākais",
|
"Latest": "Jaunākais",
|
||||||
|
@ -71,57 +71,57 @@
|
||||||
"ItemRemovedWithName": "{0} tika noņemts no bibliotēkas",
|
"ItemRemovedWithName": "{0} tika noņemts no bibliotēkas",
|
||||||
"ItemAddedWithName": "{0} tika pievienots bibliotēkai",
|
"ItemAddedWithName": "{0} tika pievienots bibliotēkai",
|
||||||
"HeaderLiveTV": "Tiešraides TV",
|
"HeaderLiveTV": "Tiešraides TV",
|
||||||
"HeaderContinueWatching": "Turpināt Skatīšanos",
|
"HeaderContinueWatching": "Turpini skatīties",
|
||||||
"HeaderAlbumArtists": "Albumu Izpildītāji",
|
"HeaderAlbumArtists": "Albumu izpildītāji",
|
||||||
"Genres": "Žanri",
|
"Genres": "Žanri",
|
||||||
"Folders": "Mapes",
|
"Folders": "Mapes",
|
||||||
"Favorites": "Favorīti",
|
"Favorites": "Izlase",
|
||||||
"FailedLoginAttemptWithUserName": "Neizdevies pieslēgšanās mēģinājums no {0}",
|
"FailedLoginAttemptWithUserName": "Neizdevies ieiešanas mēģinājums no {0}",
|
||||||
"DeviceOnlineWithName": "{0} ir pievienojies",
|
"DeviceOnlineWithName": "Savienojums ar {0} ir izveidots",
|
||||||
"DeviceOfflineWithName": "{0} ir atvienojies",
|
"DeviceOfflineWithName": "Savienojums ar {0} ir pārtraukts",
|
||||||
"Collections": "Kolekcijas",
|
"Collections": "Kolekcijas",
|
||||||
"Channels": "Kanāli",
|
"Channels": "Kanāli",
|
||||||
"CameraImageUploadedFrom": "Jauns kameras attēls ir ticis augšupielādēts no {0}",
|
"CameraImageUploadedFrom": "Jauns kameras attēls tika augšupielādēts no {0}",
|
||||||
"Books": "Grāmatas",
|
"Books": "Grāmatas",
|
||||||
"Artists": "Izpildītāji",
|
"Artists": "Izpildītāji",
|
||||||
"Albums": "Albumi",
|
"Albums": "Albumi",
|
||||||
"ProviderValue": "Provider: {0}",
|
"ProviderValue": "Provider: {0}",
|
||||||
"HeaderFavoriteSongs": "Dziesmu Favorīti",
|
"HeaderFavoriteSongs": "Dziesmu izlase",
|
||||||
"HeaderFavoriteShows": "Raidījumu Favorīti",
|
"HeaderFavoriteShows": "Raidījumu izlase",
|
||||||
"HeaderFavoriteEpisodes": "Episožu Favorīti",
|
"HeaderFavoriteEpisodes": "Sēriju izlase",
|
||||||
"HeaderFavoriteArtists": "Izpildītāju Favorīti",
|
"HeaderFavoriteArtists": "Izpildītāju izlase",
|
||||||
"HeaderFavoriteAlbums": "Albumu Favorīti",
|
"HeaderFavoriteAlbums": "Albumu izlase",
|
||||||
"TaskCleanCacheDescription": "Nodzēš keša datnes, kas vairs nav sistēmai vajadzīgas.",
|
"TaskCleanCacheDescription": "Nodzēš kešatmiņas datnes, kas vairs nav sistēmai vajadzīgas.",
|
||||||
"TaskRefreshChapterImages": "Izvilkt Nodaļu Attēlus",
|
"TaskRefreshChapterImages": "Izvilkt nodaļu attēlus",
|
||||||
"TasksApplicationCategory": "Lietotne",
|
"TasksApplicationCategory": "Lietotne",
|
||||||
"TasksLibraryCategory": "Bibliotēka",
|
"TasksLibraryCategory": "Bibliotēka",
|
||||||
"TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus balstoties uz metadatu uzstādījumiem.",
|
"TaskDownloadMissingSubtitlesDescription": "Internetā meklē trūkstošus subtitrus balstoties uz metadatu uzstādījumiem.",
|
||||||
"TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošus subtitrus",
|
"TaskDownloadMissingSubtitles": "Lejupielādēt trūkstošos subtitrus",
|
||||||
"TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.",
|
"TaskRefreshChannelsDescription": "Atjauno interneta kanālu informāciju.",
|
||||||
"TaskRefreshChannels": "Atjaunot Kanālus",
|
"TaskRefreshChannels": "Atjaunot kanālus",
|
||||||
"TaskCleanTranscodeDescription": "Izdzēš trans-kodēšanas datnes, kas ir vecākas par vienu dienu.",
|
"TaskCleanTranscodeDescription": "Izdzēš transkodēšanas datnes, kas ir senākas par vienu dienu.",
|
||||||
"TaskCleanTranscode": "Iztīrīt Trans-kodēšanas Mapi",
|
"TaskCleanTranscode": "Iztīrīt transkodēšanas mapi",
|
||||||
"TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.",
|
"TaskUpdatePluginsDescription": "Lejupielādē un uzstāda atjauninājumus paplašinājumiem, kam ir uzstādīta automātiskā atjaunināšana.",
|
||||||
"TaskUpdatePlugins": "Atjaunot Paplašinājumus",
|
"TaskUpdatePlugins": "Atjaunot paplašinājumus",
|
||||||
"TaskRefreshPeopleDescription": "Atjauno metadatus aktieriem un direktoriem jūsu multivides bibliotēkā.",
|
"TaskRefreshPeopleDescription": "Atjauno metadatus aktieriem un direktoriem jūsu multivides bibliotēkā.",
|
||||||
"TaskRefreshPeople": "Atjaunot Cilvēkus",
|
"TaskRefreshPeople": "Atjaunot cilvēkus",
|
||||||
"TaskCleanLogsDescription": "Nodzēš log datnes, kas ir vairāk par {0} dienām vecas.",
|
"TaskCleanLogsDescription": "Nodzēš logdatnes, kas ir senākas par {0} dienām.",
|
||||||
"TaskCleanLogs": "Iztīrīt Logdatņu Mapi",
|
"TaskCleanLogs": "Iztīrīt logdatņu mapi",
|
||||||
"TaskRefreshLibraryDescription": "Skenē jūsu multivides bibliotēku, lai atrastu jaunas datnes, un atsvaidzina metadatus.",
|
"TaskRefreshLibraryDescription": "Skenē jūsu multivides bibliotēku, lai atrastu jaunas datnes, un atsvaidzina metadatus.",
|
||||||
"TaskRefreshLibrary": "Skenēt Multivides Bibliotēku",
|
"TaskRefreshLibrary": "Skenēt multivides bibliotēku",
|
||||||
"TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.",
|
"TaskRefreshChapterImagesDescription": "Izveido sīktēlus priekš video ar sadaļām.",
|
||||||
"TaskCleanCache": "Iztīrīt Kešošanas Mapi",
|
"TaskCleanCache": "Iztīrīt kešatmiņas mapi",
|
||||||
"TasksChannelsCategory": "Interneta Kanāli",
|
"TasksChannelsCategory": "Interneta kanāli",
|
||||||
"TasksMaintenanceCategory": "Apkope",
|
"TasksMaintenanceCategory": "Apkope",
|
||||||
"Forced": "Piespiests",
|
"Forced": "Piespiedu",
|
||||||
"TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.",
|
"TaskCleanActivityLogDescription": "Nodzēš darbību žurnāla ierakstus, kuri ir vecāki par doto vecumu.",
|
||||||
"TaskCleanActivityLog": "Notīrīt Darbību Žurnālu",
|
"TaskCleanActivityLog": "Notīrīt darbību žurnālu",
|
||||||
"Undefined": "Nenoteikts",
|
"Undefined": "Nenoteikts",
|
||||||
"Default": "Noklusējuma",
|
"Default": "Noklusējuma",
|
||||||
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Uzdevum palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
|
"TaskOptimizeDatabaseDescription": "Saspiež datubāzi un atbrīvo atmiņu. Šī uzdevuma palaišana pēc bibliotēku skenēšanas vai citām, ar datubāzi saistītām, izmaiņām iespējams uzlabos ātrdarbību.",
|
||||||
"TaskOptimizeDatabase": "Optimizēt datubāzi",
|
"TaskOptimizeDatabase": "Optimizēt datubāzi",
|
||||||
"External": "Ārējais",
|
"External": "Ārējais",
|
||||||
"HearingImpaired": "Ar dzirdes traucējumiem",
|
"HearingImpaired": "Ar dzirdes traucējumiem",
|
||||||
"TaskKeyframeExtractor": "Atslēgkadru Ekstraktors",
|
"TaskKeyframeExtractor": "Atslēgkadru ekstraktors",
|
||||||
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs."
|
"TaskKeyframeExtractorDescription": "Ekstraktē atslēgkadrus no video failiem lai izveidotu precīzākus HLS atskaņošanas sarakstus. Šis process var būt ilgs."
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,5 +121,7 @@
|
||||||
"TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്കാൻ ചെയ്തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്തതിന് ശേഷം ഈ ടാസ്ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.",
|
"TaskOptimizeDatabaseDescription": "ഡാറ്റാബേസ് ചുരുക്കുകയും സ്വതന്ത്ര ഇടം വെട്ടിച്ചുരുക്കുകയും ചെയ്യുന്നു. ലൈബ്രറി സ്കാൻ ചെയ്തതിനുശേഷം അല്ലെങ്കിൽ ഡാറ്റാബേസ് പരിഷ്ക്കരണങ്ങളെ സൂചിപ്പിക്കുന്ന മറ്റ് മാറ്റങ്ങൾ ചെയ്തതിന് ശേഷം ഈ ടാസ്ക് പ്രവർത്തിപ്പിക്കുന്നത് പ്രകടനം മെച്ചപ്പെടുത്തും.",
|
||||||
"TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക",
|
"TaskOptimizeDatabase": "ഡാറ്റാബേസ് ഒപ്റ്റിമൈസ് ചെയ്യുക",
|
||||||
"HearingImpaired": "കേൾവി തകരാറുകൾ",
|
"HearingImpaired": "കേൾവി തകരാറുകൾ",
|
||||||
"External": "പുറമേയുള്ള"
|
"External": "പുറമേയുള്ള",
|
||||||
|
"TaskKeyframeExtractorDescription": "കൂടുതൽ കൃത്യമായ HLS പ്ലേലിസ്റ്റുകൾ സൃഷ്ടിക്കുന്നതിന് വീഡിയോ ഫയലുകളിൽ നിന്ന് കീഫ്രെയിമുകൾ എക്സ്ട്രാക്റ്റ് ചെയ്യുന്നു. ഈ പ്രവർത്തനം പൂർത്തിയാവാൻ കുറച്ചധികം സമയം എടുത്തേക്കാം.",
|
||||||
|
"TaskKeyframeExtractor": "കീഫ്രെയിം എക്സ്ട്രാക്റ്റർ"
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.",
|
"TaskKeyframeExtractorDescription": "Haalt keyframes uit videobestanden om preciezere HLS-afspeellijsten te maken. Deze taak kan lang duren.",
|
||||||
"TaskKeyframeExtractor": "Keyframe-uitpakker",
|
"TaskKeyframeExtractor": "Keyframe-uitpakker",
|
||||||
"External": "Extern",
|
"External": "Extern",
|
||||||
"HearingImpaired": "Slechthorend"
|
"HearingImpaired": "Slechthorend",
|
||||||
|
"TaskRefreshTrickplayImages": "Trickplay-afbeeldingen genereren",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Genereert trickplay-afbeeldingen voor video's in bibliotheken waarvoor dit is ingeschakeld."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"External": "Zewnętrzny",
|
"External": "Zewnętrzny",
|
||||||
"TaskKeyframeExtractorDescription": "Wyodrębnia klatki kluczowe z plików wideo w celu utworzenia bardziej precyzyjnych list odtwarzania HLS. To zadanie może trwać przez długi czas.",
|
"TaskKeyframeExtractorDescription": "Wyodrębnia klatki kluczowe z plików wideo w celu utworzenia bardziej precyzyjnych list odtwarzania HLS. To zadanie może trwać przez długi czas.",
|
||||||
"TaskKeyframeExtractor": "Ekstraktor klatek kluczowych",
|
"TaskKeyframeExtractor": "Ekstraktor klatek kluczowych",
|
||||||
"HearingImpaired": "Niedosłyszący"
|
"HearingImpaired": "Niedosłyszący",
|
||||||
|
"TaskRefreshTrickplayImages": "Generuj obrazy trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Tworzy podglądy trickplay dla filmów we włączonych bibliotekach."
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,8 +104,8 @@
|
||||||
"TaskRefreshPeople": "Atualizar Pessoas",
|
"TaskRefreshPeople": "Atualizar Pessoas",
|
||||||
"TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
|
"TaskCleanLogsDescription": "Apagar ficheiros de log que têm mais de {0} dias.",
|
||||||
"TaskCleanLogs": "Limpar a Diretoria de Logs",
|
"TaskCleanLogs": "Limpar a Diretoria de Logs",
|
||||||
"TaskRefreshLibraryDescription": "Scannear a biblioteca de música para novos ficheiros e atualizar os metadados.",
|
"TaskRefreshLibraryDescription": "Analisar a biblioteca de música para novos ficheiros e atualizar os metadados.",
|
||||||
"TaskRefreshLibrary": "Scannear Biblioteca de Música",
|
"TaskRefreshLibrary": "Analisar Biblioteca de Música",
|
||||||
"TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
|
"TaskRefreshChapterImagesDescription": "Criar thumbnails para os vídeos que têm capítulos.",
|
||||||
"TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
|
"TaskRefreshChapterImages": "Extrair Imagens dos Capítulos",
|
||||||
"TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
|
"TaskCleanCacheDescription": "Apagar ficheiros em cache que já não são necessários.",
|
||||||
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.",
|
"TaskKeyframeExtractorDescription": "Extrai quadros-chave de ficheiros de video para criar listas de reprodução HLS mais precisas. Esta tarefa pode demorar algum tempo.",
|
||||||
"TaskKeyframeExtractor": "Extrator de Quadros-chave",
|
"TaskKeyframeExtractor": "Extrator de Quadros-chave",
|
||||||
"External": "Externo",
|
"External": "Externo",
|
||||||
"HearingImpaired": "Surdo"
|
"HearingImpaired": "Surdo",
|
||||||
|
"TaskRefreshTrickplayImages": "Gerar imagens de truques",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Cria vizualizações de truques para videos nas librarias ativas."
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
"Application": "Aplicação",
|
"Application": "Aplicação",
|
||||||
"AppDeviceValues": "Aplicação: {0}, Dispositivo: {1}",
|
"AppDeviceValues": "Aplicação: {0}, Dispositivo: {1}",
|
||||||
"TaskCleanCache": "Limpar Diretório de Cache",
|
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||||
"TasksApplicationCategory": "Aplicativo",
|
"TasksApplicationCategory": "Aplicação",
|
||||||
"TasksLibraryCategory": "Biblioteca",
|
"TasksLibraryCategory": "Biblioteca",
|
||||||
"TasksMaintenanceCategory": "Manutenção",
|
"TasksMaintenanceCategory": "Manutenção",
|
||||||
"TaskRefreshChannels": "Atualizar Canais",
|
"TaskRefreshChannels": "Atualizar Canais",
|
||||||
|
@ -123,5 +123,7 @@
|
||||||
"External": "Externo",
|
"External": "Externo",
|
||||||
"HearingImpaired": "Problemas auditivos",
|
"HearingImpaired": "Problemas auditivos",
|
||||||
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
"TaskKeyframeExtractor": "Extrator de quadro-chave",
|
||||||
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo."
|
"TaskKeyframeExtractorDescription": "Retira frames chave do video para criar listas HLS precisas. Esta tarefa pode correr durante algum tempo.",
|
||||||
|
"TaskRefreshTrickplayImages": "Gerar miniaturas de vídeo",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Cria miniaturas de vídeo para vídeos nas bibliotecas definidas."
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,5 +123,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
|
"TaskKeyframeExtractorDescription": "Extrage cadrele cheie din fișierele video pentru a crea liste de redare HLS mai precise. Această sarcină poate rula o perioadă lungă de timp.",
|
||||||
"External": "Extern",
|
"External": "Extern",
|
||||||
"TaskKeyframeExtractor": "Extractor de cadre cheie",
|
"TaskKeyframeExtractor": "Extractor de cadre cheie",
|
||||||
"HearingImpaired": "Ascultare Impară"
|
"HearingImpaired": "Ascultare Impară",
|
||||||
|
"TaskRefreshTrickplayImages": "Generează imagini Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Generează previzualizările trickplay pentru videourile din librăriile selectate."
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.",
|
"TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.",
|
||||||
"TaskKeyframeExtractor": "Извлечение ключевых кадров",
|
"TaskKeyframeExtractor": "Извлечение ключевых кадров",
|
||||||
"External": "Внешние",
|
"External": "Внешние",
|
||||||
"HearingImpaired": "Для слабослышащих"
|
"HearingImpaired": "Для слабослышащих",
|
||||||
|
"TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена."
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -124,5 +124,7 @@
|
||||||
"TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.",
|
"TaskKeyframeExtractorDescription": "Extrahuje kľúčové snímky z video súborov na vytvorenie presnejších HLS playlistov. Táto úloha môže trvať dlhšiu dobu.",
|
||||||
"TaskKeyframeExtractor": "Extraktor kľúčových snímkov",
|
"TaskKeyframeExtractor": "Extraktor kľúčových snímkov",
|
||||||
"External": "Externé",
|
"External": "Externé",
|
||||||
"HearingImpaired": "Sluchovo Postihnutý"
|
"HearingImpaired": "Sluchovo postihnutí",
|
||||||
|
"TaskRefreshTrickplayImages": "Generovanie obrázkov Trickplay",
|
||||||
|
"TaskRefreshTrickplayImagesDescription": "Vytvára trickplay náhľady pre videá v povolených knižniciach."
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue