Merge branch 'master' into media-type

pull/9762/head
Cody Robibero 6 months ago committed by GitHub
commit 892973a9e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1 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@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1 uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1 uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5

@ -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 }}

@ -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 }}

@ -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 }}

@ -2,7 +2,7 @@ name: Stale Check
on: on:
schedule: schedule:
- cron: '30 */12 * * *' - cron: '30 1 * * *'
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@ -19,11 +19,12 @@ jobs:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 - 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: |-
@ -41,7 +42,8 @@ jobs:
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
with: with:
repo-token: ${{ secrets.JF_BOT_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
operations-per-run: 75 ascending: true
operations-per-run: 150
# The merge conflict action will remove the label when updated # The merge conflict action will remove the label when updated
remove-stale-when-updated: false remove-stale-when-updated: false
days-before-stale: -1 days-before-stale: -1

@ -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,7 @@
- [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)
# Emby Contributors # Emby Contributors

@ -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.12" /> <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>

@ -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>

@ -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,69 @@
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.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
});
}
}

@ -23,10 +23,8 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Rssdp; using Rssdp;
using Rssdp.Infrastructure; using Rssdp.Infrastructure;
@ -49,14 +47,13 @@ namespace Emby.Dlna.Main
private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IDeviceDiscovery _deviceDiscovery; private readonly IDeviceDiscovery _deviceDiscovery;
private readonly ISocketFactory _socketFactory; private readonly ISsdpCommunicationsServer _communicationsServer;
private readonly INetworkManager _networkManager; private readonly INetworkManager _networkManager;
private readonly object _syncLock = new object(); private readonly object _syncLock = new();
private readonly bool _disabled; private readonly bool _disabled;
private PlayToManager _manager; private PlayToManager _manager;
private SsdpDevicePublisher _publisher; private SsdpDevicePublisher _publisher;
private ISsdpCommunicationsServer _communicationsServer;
private bool _disposed; private bool _disposed;
@ -75,10 +72,8 @@ namespace Emby.Dlna.Main
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IDeviceDiscovery deviceDiscovery, IDeviceDiscovery deviceDiscovery,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
ISocketFactory socketFactory, ISsdpCommunicationsServer communicationsServer,
INetworkManager networkManager, INetworkManager networkManager)
IUserViewManager userViewManager,
ITVSeriesManager tvSeriesManager)
{ {
_config = config; _config = config;
_appHost = appHost; _appHost = appHost;
@ -93,37 +88,10 @@ namespace Emby.Dlna.Main
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery; _deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_socketFactory = socketFactory; _communicationsServer = communicationsServer;
_networkManager = networkManager; _networkManager = networkManager;
_logger = loggerFactory.CreateLogger<DlnaEntryPoint>(); _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); var netConfig = config.GetConfiguration<NetworkConfiguration>(NetworkConfigurationStore.StoreKey);
_disabled = appHost.ListenWithHttps && netConfig.RequireHttps; _disabled = appHost.ListenWithHttps && netConfig.RequireHttps;
@ -133,19 +101,6 @@ namespace Emby.Dlna.Main
} }
} }
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() public async Task RunAsync()
{ {
await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false); await ((DlnaManager)_dlnaManager).InitProfilesAsync().ConfigureAwait(false);
@ -172,9 +127,7 @@ namespace Emby.Dlna.Main
private void ReloadComponents() private void ReloadComponents()
{ {
var options = _config.GetDlnaConfiguration(); var options = _config.GetDlnaConfiguration();
Enabled = options.EnableServer; StartDeviceDiscovery();
StartSsdpHandler();
if (options.EnableServer) if (options.EnableServer)
{ {
@ -195,36 +148,11 @@ namespace Emby.Dlna.Main
} }
} }
private void StartSsdpHandler() private void StartDeviceDiscovery()
{
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 try
{ {
if (communicationsServer is not null) ((DeviceDiscovery)_deviceDiscovery).Start(_communicationsServer);
{
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -232,19 +160,6 @@ namespace Emby.Dlna.Main
} }
} }
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) public void StartDevicePublisher(Configuration.DlnaOptions options)
{ {
if (_publisher is not null) if (_publisher is not null)
@ -317,7 +232,7 @@ namespace Emby.Dlna.Main
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
}; };
SetProperies(device, fullService); SetProperties(device, fullService);
_publisher.AddDevice(device); _publisher.AddDevice(device);
var embeddedDevices = new[] var embeddedDevices = new[]
@ -338,13 +253,13 @@ namespace Emby.Dlna.Main
// This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc. // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
}; };
SetProperies(embeddedDevice, subDevice); SetProperties(embeddedDevice, subDevice);
device.AddDevice(embeddedDevice); device.AddDevice(embeddedDevice);
} }
} }
} }
private string CreateUuid(string text) private static string CreateUuid(string text)
{ {
if (!Guid.TryParse(text, out var guid)) if (!Guid.TryParse(text, out var guid))
{ {
@ -354,15 +269,14 @@ namespace Emby.Dlna.Main
return guid.ToString("D", CultureInfo.InvariantCulture); return guid.ToString("D", CultureInfo.InvariantCulture);
} }
private void SetProperies(SsdpDevice device, string fullDeviceType) private static void SetProperties(SsdpDevice device, string fullDeviceType)
{ {
var service = fullDeviceType.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase).Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase); var serviceParts = fullDeviceType
.Replace("urn:", string.Empty, StringComparison.OrdinalIgnoreCase)
var serviceParts = service.Split(':'); .Replace(":1", string.Empty, StringComparison.OrdinalIgnoreCase)
.Split(':');
var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
device.DeviceTypeNamespace = deviceTypeNamespace; device.DeviceTypeNamespace = serviceParts[0].Replace('.', '-');
device.DeviceClass = serviceParts[1]; device.DeviceClass = serviceParts[1];
device.DeviceType = serviceParts[2]; device.DeviceType = serviceParts[2];
} }
@ -443,20 +357,6 @@ namespace Emby.Dlna.Main
DisposeDevicePublisher(); DisposeDevicePublisher();
DisposePlayToManager(); 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; _disposed = true;
} }
} }

@ -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, "&amp;");
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);
return null; var xmlString = await sr.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
// find and replace unescaped ampersands (&)
xmlString = EscapeAmpersandRegex().Replace(xmlString, "&amp;");
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;
}
} }
} }
} }

@ -684,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)
{ {

@ -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>

@ -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>

@ -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;
@ -450,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)
@ -563,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>();
@ -576,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>();
@ -913,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;
@ -948,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)

@ -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)
@ -3535,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;
@ -4376,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;
} }

@ -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)

@ -525,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);
} }
@ -635,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);
} }
@ -645,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();
@ -675,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)
{ {
@ -1514,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 };
} }
@ -1543,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));
} }
@ -1678,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;
@ -1701,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;
@ -1736,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)
@ -2065,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;
} }
@ -2082,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)
{ {
@ -2116,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)
{ {
@ -2147,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);
@ -2161,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));
@ -2207,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));
@ -2269,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);
@ -2277,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));
@ -2334,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)
{ {
@ -2343,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;
@ -2378,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();
@ -2850,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);

@ -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);
} }
} }

@ -626,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)
@ -665,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)
{ {
@ -102,7 +102,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
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 ||

@ -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)

@ -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;
} }

@ -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))
{ {

@ -20,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
}; };
@ -63,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,11 +305,11 @@ 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 };
} }

@ -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 @@
"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"
}

@ -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": "Есту қабілеті нашарға"
} }

@ -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."
} }

@ -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."
} }

@ -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."
} }

@ -124,5 +124,7 @@
"TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.", "TaskKeyframeExtractorDescription": "Извлекаются ключевые кадры из видеофайлов для создания более точных списков плей-листов HLS. Эта задача может выполняться в течение длительного времени.",
"TaskKeyframeExtractor": "Извлечение ключевых кадров", "TaskKeyframeExtractor": "Извлечение ключевых кадров",
"External": "Внешние", "External": "Внешние",
"HearingImpaired": "Для слабослышащих" "HearingImpaired": "Для слабослышащих",
"TaskRefreshTrickplayImages": "Сгенерировать изображения для Trickplay",
"TaskRefreshTrickplayImagesDescription": "Создает предпросмотры для Trickplay для видео в библиотеках, где эта функция включена."
} }

@ -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."
} }

@ -124,5 +124,7 @@
"TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.", "TaskKeyframeExtractorDescription": "Exporterar nyckelbildrutor från videofiler för att skapa mer exakta HLS-spellistor. Denna rutin kan ta lång tid.",
"TaskKeyframeExtractor": "Extraktor för nyckelbildrutor", "TaskKeyframeExtractor": "Extraktor för nyckelbildrutor",
"External": "Extern", "External": "Extern",
"HearingImpaired": "Hörselskadad" "HearingImpaired": "Hörselskadad",
"TaskRefreshTrickplayImages": "Generera Trickplay-bilder",
"TaskRefreshTrickplayImagesDescription": "Skapar trickplay-förhandsvisningar för videor i aktiverade bibliotek."
} }

@ -102,7 +102,7 @@
"SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இல் இருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன", "SubtitleDownloadFailureFromForItem": "வசன வரிகள் {0} இல் இருந்து {1} க்கு பதிவிறக்கத் தவறிவிட்டன",
"TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.", "TaskDownloadMissingSubtitlesDescription": "மீத்தரவு உள்ளமைவின் அடிப்படையில் வசன வரிகள் காணாமல் போனதற்கு இணையத்தைத் தேடுகிறது.",
"TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.", "TaskCleanTranscodeDescription": "ஒரு நாளைக்கு மேற்பட்ட பழைய டிரான்ஸ்கோட் கோப்புகளை நீக்குகிறது.",
"TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்க கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்குகிறது மற்றும் நிறுவுகிறது.", "TaskUpdatePluginsDescription": "தானாகவே புதுப்பிக்கும்படி கட்டமைக்கப்பட்ட உட்செருகிகளுக்கான புதுப்பிப்புகளை பதிவிறக்கி நிறுவுகிறது.",
"TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.", "TaskRefreshPeopleDescription": "உங்கள் ஊடக நூலகத்தில் உள்ள நடிகர்கள் மற்றும் இயக்குனர்களுக்கான மீத்தரவை புதுப்பிக்கும்.",
"TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.", "TaskCleanLogsDescription": "{0} நாட்களுக்கு மேல் இருக்கும் பதிவு கோப்புகளை நீக்கும்.",
"TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்", "TaskCleanLogs": "பதிவு அடைவை சுத்தம் செய்யுங்கள்",
@ -123,5 +123,7 @@
"TaskKeyframeExtractorDescription": "மிகவும் துல்லியமான HLS பிளேலிஸ்ட்களை உருவாக்க வீடியோ கோப்புகளிலிருந்து கீஃப்ரேம்களைப் பிரித்தெடுக்கிறது. இந்த பணி நீண்ட காலமாக இருக்கலாம்.", "TaskKeyframeExtractorDescription": "மிகவும் துல்லியமான HLS பிளேலிஸ்ட்களை உருவாக்க வீடியோ கோப்புகளிலிருந்து கீஃப்ரேம்களைப் பிரித்தெடுக்கிறது. இந்த பணி நீண்ட காலமாக இருக்கலாம்.",
"TaskKeyframeExtractor": "கீஃப்ரேம் எக்ஸ்ட்ராக்டர்", "TaskKeyframeExtractor": "கீஃப்ரேம் எக்ஸ்ட்ராக்டர்",
"External": "வெளி", "External": "வெளி",
"HearingImpaired": "செவித்திறன் குறைபாடுடையவர்" "HearingImpaired": "செவித்திறன் குறைபாடுடையவர்",
"TaskRefreshTrickplayImages": "முன்னோட்ட படங்களை உருவாக்கு",
"TaskRefreshTrickplayImagesDescription": "செயல்பாட்டில் உள்ள தொகுப்புகளுக்கு முன்னோட்ட படங்களை உருவாக்கும்."
} }

@ -123,5 +123,7 @@
"TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.", "TaskKeyframeExtractorDescription": "Витягує ключові кадри з відеофайлів для створення більш точних списків відтворення HLS. Це завдання може виконуватися протягом тривалого часу.",
"TaskKeyframeExtractor": "Екстрактор ключових кадрів", "TaskKeyframeExtractor": "Екстрактор ключових кадрів",
"External": "Зовнішній", "External": "Зовнішній",
"HearingImpaired": "З порушеннями слуху" "HearingImpaired": "З порушеннями слуху",
"TaskRefreshTrickplayImagesDescription": "Створює trickplay-зображення для відео у ввімкнених медіатеках.",
"TaskRefreshTrickplayImages": "Створення Trickplay-зображень"
} }

@ -121,8 +121,10 @@
"Default": "默认", "Default": "默认",
"TaskOptimizeDatabaseDescription": "压缩数据库并优化可用空间,在扫描库或执行其他数据库修改后运行此任务可能会提高性能。", "TaskOptimizeDatabaseDescription": "压缩数据库并优化可用空间,在扫描库或执行其他数据库修改后运行此任务可能会提高性能。",
"TaskOptimizeDatabase": "优化数据库", "TaskOptimizeDatabase": "优化数据库",
"TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的HLS播放列表。这项任务可能需要很长时间。", "TaskKeyframeExtractorDescription": "从视频文件中提取关键帧以创建更准确的 HLS 播放列表。这项任务可能需要很长时间。",
"TaskKeyframeExtractor": "关键帧提取器", "TaskKeyframeExtractor": "关键帧提取器",
"External": "外部", "External": "外部",
"HearingImpaired": "听力障碍" "HearingImpaired": "听力障碍",
"TaskRefreshTrickplayImages": "生成时间轴缩略图",
"TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。"
} }

@ -123,5 +123,7 @@
"TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。", "TaskKeyframeExtractorDescription": "將關鍵幀從影片檔案提取出來並建立更精準的HLS播放清單。這可能需要很長時間。",
"TaskKeyframeExtractor": "關鍵幀提取器", "TaskKeyframeExtractor": "關鍵幀提取器",
"External": "外部", "External": "外部",
"HearingImpaired": "聽力障礙" "HearingImpaired": "聽力障礙",
"TaskRefreshTrickplayImages": "生成快轉縮圖",
"TaskRefreshTrickplayImagesDescription": "為啟用此設定的媒體庫生成快轉縮圖。"
} }

@ -71,25 +71,28 @@ namespace Emby.Server.Implementations.Localization
string countryCode = resource.Substring(RatingsPath.Length, 2); string countryCode = resource.Substring(RatingsPath.Length, 2);
var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase); var dict = new Dictionary<string, ParentalRating>(StringComparer.OrdinalIgnoreCase);
await using var stream = _assembly.GetManifestResourceStream(resource); var stream = _assembly.GetManifestResourceStream(resource);
using var reader = new StreamReader(stream!); // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames() await using (stream!.ConfigureAwait(false)) // shouldn't be null here, we just got the resource path from Assembly.GetManifestResourceNames()
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{ {
if (string.IsNullOrWhiteSpace(line)) using var reader = new StreamReader(stream!);
await foreach (var line in reader.ReadAllLinesAsync().ConfigureAwait(false))
{ {
continue; if (string.IsNullOrWhiteSpace(line))
} {
continue;
string[] parts = line.Split(','); }
if (parts.Length == 2
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value)) string[] parts = line.Split(',');
{ if (parts.Length == 2
var name = parts[0]; && int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out var value))
dict.Add(name, new ParentalRating(name, value)); {
} var name = parts[0];
else dict.Add(name, new ParentalRating(name, value));
{ }
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode); else
{
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
}
} }
} }

@ -1,12 +1,15 @@
#pragma warning disable CS1591
using System; using System;
using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
namespace Emby.Server.Implementations.Net namespace Emby.Server.Implementations.Net
{ {
/// <summary>
/// Factory class to create different kinds of sockets.
/// </summary>
public class SocketFactory : ISocketFactory public class SocketFactory : ISocketFactory
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -29,7 +32,7 @@ namespace Emby.Server.Implementations.Net
} }
catch catch
{ {
socket?.Dispose(); socket.Dispose();
throw; throw;
} }
@ -38,7 +41,8 @@ namespace Emby.Server.Implementations.Net
/// <inheritdoc /> /// <inheritdoc />
public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort) public Socket CreateSsdpUdpSocket(IPData bindInterface, int localPort)
{ {
ArgumentNullException.ThrowIfNull(bindInterface.Address); var interfaceAddress = bindInterface.Address;
ArgumentNullException.ThrowIfNull(interfaceAddress);
if (localPort < 0) if (localPort < 0)
{ {
@ -49,13 +53,13 @@ namespace Emby.Server.Implementations.Net
try try
{ {
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.Bind(new IPEndPoint(bindInterface.Address, localPort)); socket.Bind(new IPEndPoint(interfaceAddress, localPort));
return socket; return socket;
} }
catch catch
{ {
socket?.Dispose(); socket.Dispose();
throw; throw;
} }
@ -82,22 +86,31 @@ namespace Emby.Server.Implementations.Net
try try
{ {
var interfaceIndex = bindInterface.Index;
var interfaceIndexSwapped = (int)IPAddress.HostToNetworkOrder(interfaceIndex);
socket.MulticastLoopback = false; socket.MulticastLoopback = false;
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true); socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive); socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastInterface, interfaceIndexSwapped);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex)); if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
socket.Bind(new IPEndPoint(multicastAddress, localPort)); {
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress));
socket.Bind(new IPEndPoint(multicastAddress, localPort));
}
else
{
// Only create socket if interface supports multicast
var interfaceIndex = bindInterface.Index;
var interfaceIndexSwapped = IPAddress.HostToNetworkOrder(interfaceIndex);
socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(multicastAddress, interfaceIndex));
socket.Bind(new IPEndPoint(bindIPAddress, localPort));
}
return socket; return socket;
} }
catch catch
{ {
socket?.Dispose(); socket.Dispose();
throw; throw;
} }

@ -25,7 +25,7 @@ namespace Emby.Server.Implementations.Playlists
public override bool SupportsInheritedParentImages => false; public override bool SupportsInheritedParentImages => false;
[JsonIgnore] [JsonIgnore]
public override string CollectionType => MediaBrowser.Model.Entities.CollectionType.Playlists; public override CollectionType? CollectionType => Jellyfin.Data.Enums.CollectionType.Playlists;
protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
{ {

@ -386,11 +386,11 @@ namespace Emby.Server.Implementations.Plugins
var url = new Uri(packageInfo.ImageUrl); var url = new Uri(packageInfo.ImageUrl);
imagePath = Path.Join(path, url.Segments[^1]); imagePath = Path.Join(path, url.Segments[^1]);
await using var fileStream = AsyncFile.OpenWrite(imagePath); var fileStream = AsyncFile.OpenWrite(imagePath);
Stream? downloadStream = null;
try try
{ {
await using var downloadStream = await HttpClientFactory downloadStream = await HttpClientFactory
.CreateClient(NamedClient.Default) .CreateClient(NamedClient.Default)
.GetStreamAsync(url) .GetStreamAsync(url)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -402,6 +402,14 @@ namespace Emby.Server.Implementations.Plugins
_logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath); _logger.LogError(ex, "Failed to download image to path {Path} on disk.", imagePath);
imagePath = string.Empty; imagePath = string.Empty;
} }
finally
{
await fileStream.DisposeAsync().ConfigureAwait(false);
if (downloadStream is not null)
{
await downloadStream.DisposeAsync().ConfigureAwait(false);
}
}
} }
var manifest = new PluginManifest var manifest = new PluginManifest
@ -421,7 +429,7 @@ namespace Emby.Server.Implementations.Plugins
ImagePath = imagePath ImagePath = imagePath
}; };
if (!await ReconcileManifest(manifest, path)) if (!await ReconcileManifest(manifest, path).ConfigureAwait(false))
{ {
// An error occurred during reconciliation and saving could be undesirable. // An error occurred during reconciliation and saving could be undesirable.
return false; return false;
@ -458,7 +466,7 @@ namespace Emby.Server.Implementations.Plugins
} }
using var metaStream = File.OpenRead(metafile); using var metaStream = File.OpenRead(metafile);
var localManifest = await JsonSerializer.DeserializeAsync<PluginManifest>(metaStream, _jsonOptions); var localManifest = await JsonSerializer.DeserializeAsync<PluginManifest>(metaStream, _jsonOptions).ConfigureAwait(false);
localManifest ??= new PluginManifest(); localManifest ??= new PluginManifest();
if (!Equals(localManifest.Id, manifest.Id)) if (!Equals(localManifest.Id, manifest.Id))

@ -116,7 +116,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{ {
try try
{ {
previouslyFailedImages = File.ReadAllText(failHistoryPath) previouslyFailedImages = (await File.ReadAllTextAsync(failHistoryPath, cancellationToken).ConfigureAwait(false))
.Split('|', StringSplitOptions.RemoveEmptyEntries) .Split('|', StringSplitOptions.RemoveEmptyEntries)
.ToList(); .ToList();
} }
@ -157,7 +157,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
} }
string text = string.Join('|', previouslyFailedImages); string text = string.Join('|', previouslyFailedImages);
File.WriteAllText(failHistoryPath, text); await File.WriteAllTextAsync(failHistoryPath, text, cancellationToken).ConfigureAwait(false);
} }
numComplete++; numComplete++;

@ -19,6 +19,7 @@ using MediaBrowser.Common.Events;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Authentication; using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -48,6 +49,7 @@ namespace Emby.Server.Implementations.Session
public sealed class SessionManager : ISessionManager, IAsyncDisposable public sealed class SessionManager : ISessionManager, IAsyncDisposable
{ {
private readonly IUserDataManager _userDataManager; private readonly IUserDataManager _userDataManager;
private readonly IServerConfigurationManager _config;
private readonly ILogger<SessionManager> _logger; private readonly ILogger<SessionManager> _logger;
private readonly IEventManager _eventManager; private readonly IEventManager _eventManager;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -63,6 +65,7 @@ namespace Emby.Server.Implementations.Session
= new(StringComparer.OrdinalIgnoreCase); = new(StringComparer.OrdinalIgnoreCase);
private Timer _idleTimer; private Timer _idleTimer;
private Timer _inactiveTimer;
private DtoOptions _itemInfoDtoOptions; private DtoOptions _itemInfoDtoOptions;
private bool _disposed = false; private bool _disposed = false;
@ -71,6 +74,7 @@ namespace Emby.Server.Implementations.Session
ILogger<SessionManager> logger, ILogger<SessionManager> logger,
IEventManager eventManager, IEventManager eventManager,
IUserDataManager userDataManager, IUserDataManager userDataManager,
IServerConfigurationManager config,
ILibraryManager libraryManager, ILibraryManager libraryManager,
IUserManager userManager, IUserManager userManager,
IMusicManager musicManager, IMusicManager musicManager,
@ -84,6 +88,7 @@ namespace Emby.Server.Implementations.Session
_logger = logger; _logger = logger;
_eventManager = eventManager; _eventManager = eventManager;
_userDataManager = userDataManager; _userDataManager = userDataManager;
_config = config;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_userManager = userManager; _userManager = userManager;
_musicManager = musicManager; _musicManager = musicManager;
@ -369,6 +374,15 @@ namespace Emby.Server.Implementations.Session
session.LastPlaybackCheckIn = DateTime.UtcNow; session.LastPlaybackCheckIn = DateTime.UtcNow;
} }
if (info.IsPaused && session.LastPausedDate is null)
{
session.LastPausedDate = DateTime.UtcNow;
}
else if (!info.IsPaused)
{
session.LastPausedDate = null;
}
session.PlayState.IsPaused = info.IsPaused; session.PlayState.IsPaused = info.IsPaused;
session.PlayState.PositionTicks = info.PositionTicks; session.PlayState.PositionTicks = info.PositionTicks;
session.PlayState.MediaSourceId = info.MediaSourceId; session.PlayState.MediaSourceId = info.MediaSourceId;
@ -536,9 +550,18 @@ namespace Emby.Server.Implementations.Session
return users; return users;
} }
private void StartIdleCheckTimer() private void StartCheckTimers()
{ {
_idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)); _idleTimer ??= new Timer(CheckForIdlePlayback, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
if (_config.Configuration.InactiveSessionThreshold > 0)
{
_inactiveTimer ??= new Timer(CheckForInactiveSteams, null, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1));
}
else
{
StopInactiveCheckTimer();
}
} }
private void StopIdleCheckTimer() private void StopIdleCheckTimer()
@ -550,6 +573,15 @@ namespace Emby.Server.Implementations.Session
} }
} }
private void StopInactiveCheckTimer()
{
if (_inactiveTimer is not null)
{
_inactiveTimer.Dispose();
_inactiveTimer = null;
}
}
private async void CheckForIdlePlayback(object state) private async void CheckForIdlePlayback(object state)
{ {
var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) var playingSessions = Sessions.Where(i => i.NowPlayingItem is not null)
@ -585,13 +617,50 @@ namespace Emby.Server.Implementations.Session
playingSessions = Sessions.Where(i => i.NowPlayingItem is not null) playingSessions = Sessions.Where(i => i.NowPlayingItem is not null)
.ToList(); .ToList();
} }
else
if (playingSessions.Count == 0)
{ {
StopIdleCheckTimer(); StopIdleCheckTimer();
} }
} }
private async void CheckForInactiveSteams(object state)
{
var inactiveSessions = Sessions.Where(i =>
i.NowPlayingItem is not null
&& i.PlayState.IsPaused
&& (DateTime.UtcNow - i.LastPausedDate).Value.TotalMinutes > _config.Configuration.InactiveSessionThreshold);
foreach (var session in inactiveSessions)
{
_logger.LogDebug("Session {Session} has been inactive for {InactiveTime} minutes. Stopping it.", session.Id, _config.Configuration.InactiveSessionThreshold);
try
{
await SendPlaystateCommand(
session.Id,
session.Id,
new PlaystateRequest()
{
Command = PlaystateCommand.Stop,
ControllingUserId = session.UserId.ToString(),
SeekPositionTicks = session.PlayState?.PositionTicks
},
CancellationToken.None).ConfigureAwait(true);
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Error calling SendPlaystateCommand for stopping inactive session {Session}.", session.Id);
}
}
bool playingSessions = Sessions.Any(i => i.NowPlayingItem is not null);
if (!playingSessions)
{
StopInactiveCheckTimer();
}
}
private BaseItem GetNowPlayingItem(SessionInfo session, Guid itemId) private BaseItem GetNowPlayingItem(SessionInfo session, Guid itemId)
{ {
var item = session.FullNowPlayingItem; var item = session.FullNowPlayingItem;
@ -668,7 +737,7 @@ namespace Emby.Server.Implementations.Session
eventArgs, eventArgs,
_logger); _logger);
StartIdleCheckTimer(); StartCheckTimers();
} }
/// <summary> /// <summary>
@ -762,7 +831,7 @@ namespace Emby.Server.Implementations.Session
session.StartAutomaticProgress(info); session.StartAutomaticProgress(info);
} }
StartIdleCheckTimer(); StartCheckTimers();
} }
private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info) private void OnPlaybackProgress(User user, BaseItem item, PlaybackProgressInfo info)
@ -1384,10 +1453,15 @@ namespace Emby.Server.Implementations.Session
return AuthenticateNewSessionInternal(request, false); return AuthenticateNewSessionInternal(request, false);
} }
private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword) internal async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{ {
CheckDisposed(); CheckDisposed();
ArgumentException.ThrowIfNullOrEmpty(request.App);
ArgumentException.ThrowIfNullOrEmpty(request.DeviceId);
ArgumentException.ThrowIfNullOrEmpty(request.DeviceName);
ArgumentException.ThrowIfNullOrEmpty(request.AppVersion);
User user = null; User user = null;
if (!request.UserId.Equals(default)) if (!request.UserId.Equals(default))
{ {
@ -1448,8 +1522,11 @@ namespace Emby.Server.Implementations.Session
return returnResult; return returnResult;
} }
private async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName) internal async Task<string> GetAuthorizationToken(User user, string deviceId, string app, string appVersion, string deviceName)
{ {
// This should be validated above, but if it isn't don't delete all tokens.
ArgumentException.ThrowIfNullOrEmpty(deviceId);
var existing = (await _deviceManager.GetDevices( var existing = (await _deviceManager.GetDevices(
new DeviceQuery new DeviceQuery
{ {
@ -1798,6 +1875,12 @@ namespace Emby.Server.Implementations.Session
_idleTimer = null; _idleTimer = null;
} }
if (_inactiveTimer is not null)
{
await _inactiveTimer.DisposeAsync().ConfigureAwait(false);
_inactiveTimer = null;
}
await _shutdownCallback.DisposeAsync().ConfigureAwait(false); await _shutdownCallback.DisposeAsync().ConfigureAwait(false);
_deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated; _deviceManager.DeviceOptionsUpdated -= OnDeviceManagerDeviceOptionsUpdated;

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
@ -14,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.AiredEpisodeOrder; public ItemSortBy Type => ItemSortBy.AiredEpisodeOrder;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
@ -16,7 +17,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.AlbumArtist; public ItemSortBy Type => ItemSortBy.AlbumArtist;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

@ -1,4 +1,5 @@
using System; using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
@ -15,7 +16,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.Album; public ItemSortBy Type => ItemSortBy.Album;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

@ -1,4 +1,5 @@
using System; using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
@ -12,7 +13,7 @@ namespace Emby.Server.Implementations.Sorting
public class ArtistComparer : IBaseItemComparer public class ArtistComparer : IBaseItemComparer
{ {
/// <inheritdoc /> /// <inheritdoc />
public string Name => ItemSortBy.Artist; public ItemSortBy Type => ItemSortBy.Artist;
/// <inheritdoc /> /// <inheritdoc />
public int Compare(BaseItem? x, BaseItem? y) public int Compare(BaseItem? x, BaseItem? y)

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -13,7 +14,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.CommunityRating; public ItemSortBy Type => ItemSortBy.CommunityRating;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

@ -1,3 +1,4 @@
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -13,7 +14,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.CriticRating; public ItemSortBy Type => ItemSortBy.CriticRating;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

@ -1,4 +1,5 @@
using System; using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
@ -14,7 +15,7 @@ namespace Emby.Server.Implementations.Sorting
/// Gets the name. /// Gets the name.
/// </summary> /// </summary>
/// <value>The name.</value> /// <value>The name.</value>
public string Name => ItemSortBy.DateCreated; public ItemSortBy Type => ItemSortBy.DateCreated;
/// <summary> /// <summary>
/// Compares the specified x. /// Compares the specified x.

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

Loading…
Cancel
Save