Merge branch 'master' into bindfix

This commit is contained in:
BaronGreenback 2021-02-21 00:44:14 +00:00 committed by GitHub
commit 5756c6dbad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
260 changed files with 4715 additions and 1649 deletions

View file

@ -7,7 +7,7 @@ parameters:
default: "ubuntu-latest" default: "ubuntu-latest"
- name: DotNetSdkVersion - name: DotNetSdkVersion
type: string type: string
default: 5.0.100 default: 5.0.103
jobs: jobs:
- job: CompatibilityCheck - job: CompatibilityCheck

View file

@ -4,7 +4,7 @@
default: "ubuntu-latest" default: "ubuntu-latest"
- name: GeneratorVersion - name: GeneratorVersion
type: string type: string
default: "5.0.0-beta2" default: "5.0.1"
jobs: jobs:
- job: GenerateApiClients - job: GenerateApiClients

View file

@ -1,7 +1,7 @@
parameters: parameters:
LinuxImage: 'ubuntu-latest' LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj' RestoreBuildProjects: 'Jellyfin.Server/Jellyfin.Server.csproj'
DotNetSdkVersion: 5.0.100 DotNetSdkVersion: 5.0.103
jobs: jobs:
- job: Build - job: Build

View file

@ -193,6 +193,10 @@ jobs:
pool: pool:
vmImage: 'ubuntu-latest' vmImage: 'ubuntu-latest'
variables:
- name: JellyfinVersion
value: $[replace(variables['Build.SourceBranch'],'refs/tags/v','')]
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET 5.0 sdk' displayName: 'Use .NET 5.0 sdk'
@ -204,9 +208,15 @@ jobs:
displayName: 'Build Stable Nuget packages' displayName: 'Build Stable Nuget packages'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs: inputs:
command: 'pack' command: 'custom'
packagesToPack: 'Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj' projects: |
versioningScheme: 'off' Jellyfin.Data/Jellyfin.Data.csproj
MediaBrowser.Common/MediaBrowser.Common.csproj
MediaBrowser.Controller/MediaBrowser.Controller.csproj
MediaBrowser.Model/MediaBrowser.Model.csproj
Emby.Naming/Emby.Naming.csproj
custom: 'pack'
arguments: -o $(Build.ArtifactStagingDirectory) -p:Version=$(JellyfinVersion)
- task: DotNetCoreCLI@2 - task: DotNetCoreCLI@2
displayName: 'Build Unstable Nuget packages' displayName: 'Build Unstable Nuget packages'
@ -233,7 +243,7 @@ jobs:
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')
inputs: inputs:
command: 'push' command: 'push'
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg;$(Build.ArtifactStagingDirectory)/**/*.snupkg' packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
nuGetFeedType: 'external' nuGetFeedType: 'external'
publishFeedCredentials: 'NugetOrg' publishFeedCredentials: 'NugetOrg'
allowPackageConflicts: true # This ignores an error if the version already exists allowPackageConflicts: true # This ignores an error if the version already exists

View file

@ -10,7 +10,7 @@ parameters:
default: "tests/**/*Tests.csproj" default: "tests/**/*Tests.csproj"
- name: DotNetSdkVersion - name: DotNetSdkVersion
type: string type: string
default: 5.0.100 default: 5.0.103
jobs: jobs:
- job: Test - job: Test

View file

@ -6,7 +6,7 @@ variables:
- name: RestoreBuildProjects - name: RestoreBuildProjects
value: 'Jellyfin.Server/Jellyfin.Server.csproj' value: 'Jellyfin.Server/Jellyfin.Server.csproj'
- name: DotNetSdkVersion - name: DotNetSdkVersion
value: 5.0.100 value: 5.0.103
pr: pr:
autoCancel: true autoCancel: true

View file

@ -1,30 +0,0 @@
---
kind: pipeline
name: build-debug
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
- name: build
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "Jellyfin.Server" --configuration Debug --output "../ci/ci-debug"
---
kind: pipeline
name: build-release
steps:
- name: submodules
image: docker:git
commands:
- git submodule update --init --recursive
- name: build
image: microsoft/dotnet:2-sdk
commands:
- dotnet publish "Jellyfin.Server" --configuration Release --output "../ci/ci-release"

View file

@ -24,7 +24,7 @@ jobs:
- name: Setup .NET Core - name: Setup .NET Core
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
dotnet-version: '5.0.100' dotnet-version: '5.0.x'
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v1 uses: github/codeql-action/init@v1
with: with:

View file

@ -80,6 +80,7 @@
- [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)
- [obradovichv](https://github.com/obradovichv)
- [oddstr13](https://github.com/oddstr13) - [oddstr13](https://github.com/oddstr13)
- [orryverducci](https://github.com/orryverducci) - [orryverducci](https://github.com/orryverducci)
- [petermcneil](https://github.com/petermcneil) - [petermcneil](https://github.com/petermcneil)

View file

@ -96,6 +96,7 @@ namespace Emby.Dlna.Didl
using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8)) using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
{ {
// If this using are changed to single lines, then write.Flush needs to be appended before the return.
using (var writer = XmlWriter.Create(builder, settings)) using (var writer = XmlWriter.Create(builder, settings))
{ {
// writer.WriteStartDocument(); // writer.WriteStartDocument();

View file

@ -315,7 +315,7 @@ namespace Emby.Dlna.Main
var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri); var uri = new UriBuilder(_appHost.GetSmartApiUrl(address.Address) + descriptorUri);
// DLNA will only work over http, so we must reset to http:// : {port} // DLNA will only work over http, so we must reset to http:// : {port}
uri.Scheme = "http://"; uri.Scheme = "http://";
uri.Port = _netConfig.PublicPort; uri.Port = _netConfig.HttpServerPortNumber;
var device = new SsdpRootDevice var device = new SsdpRootDevice
{ {

View file

@ -235,7 +235,13 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute"); _logger.LogDebug("Setting mute");
var value = mute ? 1 : 0; var value = mute ? 1 : 0;
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType, value),
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
IsMuted = mute; IsMuted = mute;
@ -270,7 +276,13 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better // Remote control will perform better
Volume = value; Volume = value;
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, rendererCommands.BuildPost(command, service.ServiceType, value)) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
rendererCommands.BuildPost(command, service.ServiceType, value),
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
@ -291,7 +303,13 @@ namespace Emby.Dlna.PlayTo
throw new InvalidOperationException("Unable to find service"); throw new InvalidOperationException("Unable to find service");
} }
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME")) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType, string.Format(CultureInfo.InvariantCulture, "{0:hh}:{0:mm}:{0:ss}", value), "REL_TIME"),
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -325,14 +343,21 @@ namespace Emby.Dlna.PlayTo
} }
var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary); var post = avCommands.BuildPost(command, service.ServiceType, url, dictionary);
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, post, header: header) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
post,
header: header,
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
await Task.Delay(50).ConfigureAwait(false); await Task.Delay(50, cancellationToken).ConfigureAwait(false);
try try
{ {
await SetPlay(avCommands, CancellationToken.None).ConfigureAwait(false); await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
} }
catch catch
{ {
@ -396,7 +421,13 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType, 1),
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
RestartTimer(true); RestartTimer(true);
@ -414,7 +445,13 @@ namespace Emby.Dlna.PlayTo
var service = GetAvTransportService(); var service = GetAvTransportService();
await new SsdpHttpClient(_httpClientFactory).SendCommandAsync(Properties.BaseUrl, service, command.Name, avCommands.BuildPost(command, service.ServiceType, 1)) await new SsdpHttpClient(_httpClientFactory)
.SendCommandAsync(
Properties.BaseUrl,
service,
command.Name,
avCommands.BuildPost(command, service.ServiceType, 1),
cancellationToken: cancellationToken)
.ConfigureAwait(false); .ConfigureAwait(false);
TransportState = TransportState.Paused; TransportState = TransportState.Paused;
@ -990,7 +1027,7 @@ namespace Emby.Dlna.PlayTo
var deviceProperties = new DeviceInfo() var deviceProperties = new DeviceInfo()
{ {
Name = string.Join(" ", friendlyNames), Name = string.Join(' ', friendlyNames),
BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port) BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port)
}; };

View file

@ -777,7 +777,7 @@ namespace Emby.Dlna.PlayTo
var currentWait = 0; var currentWait = 0;
while (_device.TransportState != TransportState.Playing && currentWait < MaxWait) while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
{ {
await Task.Delay(Interval).ConfigureAwait(false); await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
currentWait += Interval; currentWait += Interval;
} }
@ -896,16 +896,16 @@ namespace Emby.Dlna.PlayTo
var parts = url.Split('/'); var parts = url.Split('/');
for (var i = 0; i < parts.Length; i++) for (var i = 0; i < parts.Length - 1; i++)
{ {
var part = parts[i]; var part = parts[i];
if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) || if (string.Equals(part, "audio", StringComparison.OrdinalIgnoreCase) ||
string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase)) string.Equals(part, "videos", StringComparison.OrdinalIgnoreCase))
{ {
if (parts.Length > i + 1) if (Guid.TryParse(parts[i + 1], out var result))
{ {
return Guid.Parse(parts[i + 1]); return result;
} }
} }
} }

View file

@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification Identification = new DeviceIdentification
{ {
FriendlyName = @"KDL-\d{2}[EHLNPB]X\d[01]\d.*", FriendlyName = @"KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
Manufacturer = "Sony", Manufacturer = "Sony",
Headers = new[] Headers = new[]
@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "X-AV-Client-Info", Name = "X-AV-Client-Info",
Value = @".*KDL-\d{2}[EHLNPB]X\d[01]\d.*", Value = @".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*",
Match = HeaderMatchType.Regex Match = HeaderMatchType.Regex
} }
} }

View file

@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification Identification = new DeviceIdentification
{ {
FriendlyName = @"KDL-\d{2}([A-Z]X\d2\d|CX400).*", FriendlyName = @"KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
Manufacturer = "Sony", Manufacturer = "Sony",
Headers = new[] Headers = new[]
@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "X-AV-Client-Info", Name = "X-AV-Client-Info",
Value = @".*KDL-\d{2}([A-Z]X\d2\d|CX400).*", Value = @".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*",
Match = HeaderMatchType.Regex Match = HeaderMatchType.Regex
} }
} }

View file

@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification Identification = new DeviceIdentification
{ {
FriendlyName = @"KDL-\d{2}[A-Z]X\d5(\d|G).*", FriendlyName = @"KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
Manufacturer = "Sony", Manufacturer = "Sony",
Headers = new[] Headers = new[]
@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "X-AV-Client-Info", Name = "X-AV-Client-Info",
Value = @".*KDL-\d{2}[A-Z]X\d5(\d|G).*", Value = @".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*",
Match = HeaderMatchType.Regex Match = HeaderMatchType.Regex
} }
} }

View file

@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification Identification = new DeviceIdentification
{ {
FriendlyName = @"KDL-\d{2}[WR][5689]\d{2}A.*", FriendlyName = @"KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
Manufacturer = "Sony", Manufacturer = "Sony",
Headers = new[] Headers = new[]
@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "X-AV-Client-Info", Name = "X-AV-Client-Info",
Value = @".*KDL-\d{2}[WR][5689]\d{2}A.*", Value = @".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*",
Match = HeaderMatchType.Regex Match = HeaderMatchType.Regex
} }
} }

View file

@ -13,7 +13,7 @@ namespace Emby.Dlna.Profiles
Identification = new DeviceIdentification Identification = new DeviceIdentification
{ {
FriendlyName = @"(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*", FriendlyName = @"(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
Manufacturer = "Sony", Manufacturer = "Sony",
Headers = new[] Headers = new[]
@ -21,7 +21,7 @@ namespace Emby.Dlna.Profiles
new HttpHeaderInfo new HttpHeaderInfo
{ {
Name = "X-AV-Client-Info", Name = "X-AV-Client-Info",
Value = @".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*", Value = @".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*",
Match = HeaderMatchType.Regex Match = HeaderMatchType.Regex
} }
} }

View file

@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2010)</Name> <Name>Sony Bravia (2010)</Name>
<Identification> <Identification>
<FriendlyName>KDL-\d{2}[EHLNPB]X\d[01]\d.*</FriendlyName> <FriendlyName>KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*</FriendlyName>
<Manufacturer>Sony</Manufacturer> <Manufacturer>Sony</Manufacturer>
<Headers> <Headers>
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[EHLNPB]X\d[01]\d.*" match="Regex" /> <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[EHLNPB]X[0-9][01][0-9].*" match="Regex" />
</Headers> </Headers>
</Identification> </Identification>
<Manufacturer>Microsoft Corporation</Manufacturer> <Manufacturer>Microsoft Corporation</Manufacturer>

View file

@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2011)</Name> <Name>Sony Bravia (2011)</Name>
<Identification> <Identification>
<FriendlyName>KDL-\d{2}([A-Z]X\d2\d|CX400).*</FriendlyName> <FriendlyName>KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*</FriendlyName>
<Manufacturer>Sony</Manufacturer> <Manufacturer>Sony</Manufacturer>
<Headers> <Headers>
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}([A-Z]X\d2\d|CX400).*" match="Regex" /> <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}([A-Z]X[0-9]2[0-9]|CX400).*" match="Regex" />
</Headers> </Headers>
</Identification> </Identification>
<Manufacturer>Microsoft Corporation</Manufacturer> <Manufacturer>Microsoft Corporation</Manufacturer>

View file

@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2012)</Name> <Name>Sony Bravia (2012)</Name>
<Identification> <Identification>
<FriendlyName>KDL-\d{2}[A-Z]X\d5(\d|G).*</FriendlyName> <FriendlyName>KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*</FriendlyName>
<Manufacturer>Sony</Manufacturer> <Manufacturer>Sony</Manufacturer>
<Headers> <Headers>
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[A-Z]X\d5(\d|G).*" match="Regex" /> <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[A-Z]X[0-9]5([0-9]|G).*" match="Regex" />
</Headers> </Headers>
</Identification> </Identification>
<Manufacturer>Microsoft Corporation</Manufacturer> <Manufacturer>Microsoft Corporation</Manufacturer>

View file

@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2013)</Name> <Name>Sony Bravia (2013)</Name>
<Identification> <Identification>
<FriendlyName>KDL-\d{2}[WR][5689]\d{2}A.*</FriendlyName> <FriendlyName>KDL-[0-9]{2}[WR][5689][0-9]{2}A.*</FriendlyName>
<Manufacturer>Sony</Manufacturer> <Manufacturer>Sony</Manufacturer>
<Headers> <Headers>
<HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-\d{2}[WR][5689]\d{2}A.*" match="Regex" /> <HttpHeaderInfo name="X-AV-Client-Info" value=".*KDL-[0-9]{2}[WR][5689][0-9]{2}A.*" match="Regex" />
</Headers> </Headers>
</Identification> </Identification>
<Manufacturer>Microsoft Corporation</Manufacturer> <Manufacturer>Microsoft Corporation</Manufacturer>

View file

@ -3,10 +3,10 @@
xmlns:xsd="http://www.w3.org/2001/XMLSchema"> xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Sony Bravia (2014)</Name> <Name>Sony Bravia (2014)</Name>
<Identification> <Identification>
<FriendlyName>(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*</FriendlyName> <FriendlyName>(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*</FriendlyName>
<Manufacturer>Sony</Manufacturer> <Manufacturer>Sony</Manufacturer>
<Headers> <Headers>
<HttpHeaderInfo name="X-AV-Client-Info" value=".*(KDL-\d{2}W[5-9]\d{2}B|KDL-\d{2}R480|XBR-\d{2}X[89]\d{2}B|KD-\d{2}[SX][89]\d{3}B).*" match="Regex" /> <HttpHeaderInfo name="X-AV-Client-Info" value=".*(KDL-[0-9]{2}W[5-9][0-9]{2}B|KDL-[0-9]{2}R480|XBR-[0-9]{2}X[89][0-9]{2}B|KD-[0-9]{2}[SX][89][0-9]{3}B).*" match="Regex" />
</Headers> </Headers>
</Identification> </Identification>
<Manufacturer>Microsoft Corporation</Manufacturer> <Manufacturer>Microsoft Corporation</Manufacturer>

View file

@ -15,13 +15,13 @@ namespace Emby.Naming.AudioBook
/// <param name="files">List of files composing the actual audiobook.</param> /// <param name="files">List of files composing the actual audiobook.</param>
/// <param name="extras">List of extra files.</param> /// <param name="extras">List of extra files.</param>
/// <param name="alternateVersions">Alternative version of files.</param> /// <param name="alternateVersions">Alternative version of files.</param>
public AudioBookInfo(string name, int? year, List<AudioBookFileInfo>? files, List<AudioBookFileInfo>? extras, List<AudioBookFileInfo>? alternateVersions) public AudioBookInfo(string name, int? year, List<AudioBookFileInfo> files, List<AudioBookFileInfo> extras, List<AudioBookFileInfo> alternateVersions)
{ {
Name = name; Name = name;
Year = year; Year = year;
Files = files ?? new List<AudioBookFileInfo>(); Files = files;
Extras = extras ?? new List<AudioBookFileInfo>(); Extras = extras;
AlternateVersions = alternateVersions ?? new List<AudioBookFileInfo>(); AlternateVersions = alternateVersions;
} }
/// <summary> /// <summary>

View file

@ -73,7 +73,7 @@ namespace Emby.Naming.AudioBook
var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null); var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null);
var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber }); var groupedBy = stackFiles.GroupBy(file => new { file.ChapterNumber, file.PartNumber });
var nameWithReplacedDots = nameParserResult.Name.Replace(" ", "."); var nameWithReplacedDots = nameParserResult.Name.Replace(' ', '.');
foreach (var group in groupedBy) foreach (var group in groupedBy)
{ {

View file

@ -282,7 +282,13 @@ namespace Emby.Naming.Common
SupportsAbsoluteEpisodeNumbers = true SupportsAbsoluteEpisodeNumbers = true
}, },
// Case Closed (1996-2007)/Case Closed - 317.mkv // Not a Kodi rule as well, but below rule also causes false positives for triple-digit episode names
// [bar] Foo - 1 [baz] special case of below expression to prevent false positives with digits in the series name
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[\s_]*-[\s_]*(?<epnumber>[0-9]+).*$")
{
IsNamed = true
},
// /server/anything_102.mp4 // /server/anything_102.mp4
// /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv // /server/james.corden.2017.04.20.anne.hathaway.720p.hdtv.x264-crooks.mkv
// /server/anything_1996.11.14.mp4 // /server/anything_1996.11.14.mp4
@ -299,11 +305,6 @@ namespace Emby.Naming.Common
// *** End Kodi Standard Naming // *** End Kodi Standard Naming
// [bar] Foo - 1 [baz]
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>[0-9]+).*$")
{
IsNamed = true
},
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$") new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$")
{ {
IsNamed = true IsNamed = true
@ -587,7 +588,7 @@ namespace Emby.Naming.Common
AudioBookNamesExpressions = new[] AudioBookNamesExpressions = new[]
{ {
// Detect year usually in brackets after name Batman (2020) // Detect year usually in brackets after name Batman (2020)
@"^(?<name>.+?)\s*\(\s*(?<year>\d{4})\s*\)\s*$", @"^(?<name>.+?)\s*\(\s*(?<year>[0-9]{4})\s*\)\s*$",
@"^\s*(?<name>[^ ].*?)\s*$" @"^\s*(?<name>[^ ].*?)\s*$"
}; };

View file

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

View file

@ -60,7 +60,7 @@ namespace Emby.Naming.TV
bool supportSpecialAliases, bool supportSpecialAliases,
bool supportNumericSeasonFolders) bool supportNumericSeasonFolders)
{ {
var filename = Path.GetFileName(path) ?? string.Empty; string filename = Path.GetFileName(path);
if (supportSpecialAliases) if (supportSpecialAliases)
{ {

View file

@ -185,8 +185,8 @@ namespace Emby.Naming.Video
if (!string.IsNullOrEmpty(folderName) if (!string.IsNullOrEmpty(folderName)
&& folderName.Length > 1 && folderName.Length > 1
&& videos.All(i => i.Files.Count == 1 && videos.All(i => i.Files.Count == 1
&& IsEligibleForMultiVersion(folderName, i.Files[0].Path)) && IsEligibleForMultiVersion(folderName, i.Files[0].Path))
&& HaveSameYear(videos)) && HaveSameYear(videos))
{ {
var ordered = videos.OrderBy(i => i.Name).ToList(); var ordered = videos.OrderBy(i => i.Name).ToList();
@ -216,10 +216,9 @@ namespace Emby.Naming.Video
return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2; return videos.Select(i => i.Year ?? -1).Distinct().Count() < 2;
} }
private bool IsEligibleForMultiVersion(string folderName, string? testFilename) private bool IsEligibleForMultiVersion(string folderName, string testFilePath)
{ {
testFilename = Path.GetFileNameWithoutExtension(testFilename) ?? string.Empty; string testFilename = Path.GetFileNameWithoutExtension(testFilePath);
if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase)) if (testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
{ {
if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName)) if (CleanStringParser.TryClean(testFilename, _options.CleanStringRegexes, out var cleanName))
@ -233,8 +232,8 @@ namespace Emby.Naming.Video
} }
return string.IsNullOrEmpty(testFilename) return string.IsNullOrEmpty(testFilename)
|| testFilename[0].Equals('-') || testFilename[0] == '-'
|| testFilename[0].Equals('_') || testFilename[0] == '_'
|| string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty)); || string.IsNullOrWhiteSpace(Regex.Replace(testFilename, @"\[([^]]*)\]", string.Empty));
} }

View file

@ -125,7 +125,7 @@ namespace Emby.Naming.Video
/// <returns>True if is video file.</returns> /// <returns>True if is video file.</returns>
public bool IsVideoFile(string path) public bool IsVideoFile(string path)
{ {
var extension = Path.GetExtension(path) ?? string.Empty; var extension = Path.GetExtension(path);
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
} }
@ -136,7 +136,7 @@ namespace Emby.Naming.Video
/// <returns>True if is video file stub.</returns> /// <returns>True if is video file stub.</returns>
public bool IsStubFile(string path) public bool IsStubFile(string path)
{ {
var extension = Path.GetExtension(path) ?? string.Empty; var extension = Path.GetExtension(path);
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
} }

View file

@ -75,10 +75,6 @@ namespace Emby.Notifications
Type = NotificationType.VideoPlaybackStopped.ToString() Type = NotificationType.VideoPlaybackStopped.ToString()
}, },
new NotificationTypeInfo new NotificationTypeInfo
{
Type = NotificationType.CameraImageUploaded.ToString()
},
new NotificationTypeInfo
{ {
Type = NotificationType.UserLockedOut.ToString() Type = NotificationType.UserLockedOut.ToString()
}, },
@ -114,10 +110,6 @@ namespace Emby.Notifications
{ {
note.Category = _localization.GetLocalizedString("Plugin"); note.Category = _localization.GetLocalizedString("Plugin");
} }
else if (note.Type.IndexOf("CameraImageUploaded", StringComparison.OrdinalIgnoreCase) != -1)
{
note.Category = _localization.GetLocalizedString("Sync");
}
else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1) else if (note.Type.IndexOf("UserLockedOut", StringComparison.OrdinalIgnoreCase) != -1)
{ {
note.Category = _localization.GetLocalizedString("User"); note.Category = _localization.GetLocalizedString("User");

View file

@ -336,19 +336,19 @@ namespace Emby.Server.Implementations.Channels
return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result; return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
} }
private List<MediaSourceInfo> GetSavedMediaSources(BaseItem item) private MediaSourceInfo[] GetSavedMediaSources(BaseItem item)
{ {
var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json"); var path = Path.Combine(item.GetInternalMetadataPath(), "channelmediasourceinfos.json");
try try
{ {
var jsonString = File.ReadAllText(path, Encoding.UTF8); var bytes = File.ReadAllBytes(path);
return JsonSerializer.Deserialize<List<MediaSourceInfo>>(jsonString, _jsonOptions) return JsonSerializer.Deserialize<MediaSourceInfo[]>(bytes, _jsonOptions)
?? new List<MediaSourceInfo>(); ?? Array.Empty<MediaSourceInfo>();
} }
catch catch
{ {
return new List<MediaSourceInfo>(); return Array.Empty<MediaSourceInfo>();
} }
} }

View file

@ -2,6 +2,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(conn => connection.RunInTransaction(conn =>
{ {
conn.ExecuteAll(string.Join(";", queries)); conn.ExecuteAll(string.Join(';', queries));
}); });
} }
@ -142,11 +143,10 @@ namespace Emby.Server.Implementations.Data
return result[index].ReadGuidFromBlob(); return result[index].ReadGuidFromBlob();
} }
[Conditional("DEBUG")]
private static void CheckName(string name) private static void CheckName(string name)
{ {
#if DEBUG
throw new ArgumentException("Invalid param name: " + name, nameof(name)); throw new ArgumentException("Invalid param name: " + name, nameof(name));
#endif
} }
public static void TryBind(this IStatement statement, string name, double value) public static void TryBind(this IStatement statement, string name, double value)

View file

@ -687,7 +687,7 @@ namespace Emby.Server.Implementations.Data
if (item.Genres.Length > 0) if (item.Genres.Length > 0)
{ {
saveItemStatement.TryBind("@Genres", string.Join("|", item.Genres)); saveItemStatement.TryBind("@Genres", string.Join('|', item.Genres));
} }
else else
{ {
@ -749,7 +749,7 @@ namespace Emby.Server.Implementations.Data
if (item.LockedFields.Length > 0) if (item.LockedFields.Length > 0)
{ {
saveItemStatement.TryBind("@LockedFields", string.Join("|", item.LockedFields)); saveItemStatement.TryBind("@LockedFields", string.Join('|', item.LockedFields));
} }
else else
{ {
@ -758,7 +758,7 @@ namespace Emby.Server.Implementations.Data
if (item.Studios.Length > 0) if (item.Studios.Length > 0)
{ {
saveItemStatement.TryBind("@Studios", string.Join("|", item.Studios)); saveItemStatement.TryBind("@Studios", string.Join('|', item.Studios));
} }
else else
{ {
@ -785,7 +785,7 @@ namespace Emby.Server.Implementations.Data
if (item.Tags.Length > 0) if (item.Tags.Length > 0)
{ {
saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags)); saveItemStatement.TryBind("@Tags", string.Join('|', item.Tags));
} }
else else
{ {
@ -807,7 +807,7 @@ namespace Emby.Server.Implementations.Data
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0) if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
{ {
saveItemStatement.TryBind("@TrailerTypes", string.Join("|", trailer.TrailerTypes)); saveItemStatement.TryBind("@TrailerTypes", string.Join('|', trailer.TrailerTypes));
} }
else else
{ {
@ -902,7 +902,7 @@ namespace Emby.Server.Implementations.Data
if (item.ProductionLocations.Length > 0) if (item.ProductionLocations.Length > 0)
{ {
saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations)); saveItemStatement.TryBind("@ProductionLocations", string.Join('|', item.ProductionLocations));
} }
else else
{ {
@ -911,7 +911,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0) if (item.ExtraIds.Length > 0)
{ {
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds)); saveItemStatement.TryBind("@ExtraIds", string.Join('|', item.ExtraIds));
} }
else else
{ {
@ -931,7 +931,7 @@ namespace Emby.Server.Implementations.Data
string artists = null; string artists = null;
if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0) if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0)
{ {
artists = string.Join("|", hasArtists.Artists); artists = string.Join('|', hasArtists.Artists);
} }
saveItemStatement.TryBind("@Artists", artists); saveItemStatement.TryBind("@Artists", artists);
@ -940,7 +940,7 @@ namespace Emby.Server.Implementations.Data
if (item is IHasAlbumArtist hasAlbumArtists if (item is IHasAlbumArtist hasAlbumArtists
&& hasAlbumArtists.AlbumArtists.Count > 0) && hasAlbumArtists.AlbumArtists.Count > 0)
{ {
albumArtists = string.Join("|", hasAlbumArtists.AlbumArtists); albumArtists = string.Join('|', hasAlbumArtists.AlbumArtists);
} }
saveItemStatement.TryBind("@AlbumArtists", albumArtists); saveItemStatement.TryBind("@AlbumArtists", albumArtists);
@ -2549,7 +2549,7 @@ namespace Emby.Server.Implementations.Data
if (groups.Count > 0) if (groups.Count > 0)
{ {
return " Group by " + string.Join(",", groups); return " Group by " + string.Join(',', groups);
} }
return string.Empty; return string.Empty;
@ -2578,7 +2578,7 @@ namespace Emby.Server.Implementations.Data
} }
var commandText = "select " var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -2630,7 +2630,7 @@ namespace Emby.Server.Implementations.Data
} }
var commandText = "select " var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -2880,7 +2880,7 @@ namespace Emby.Server.Implementations.Data
} }
var commandText = "select " var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns)) + string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -2923,15 +2923,15 @@ namespace Emby.Server.Implementations.Data
if (EnableGroupByPresentationUniqueKey(query)) if (EnableGroupByPresentationUniqueKey(query))
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
} }
else if (query.GroupBySeriesPresentationUniqueKey) else if (query.GroupBySeriesPresentationUniqueKey)
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
} }
else else
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
} }
commandText += GetJoinUserDataText(query) commandText += GetJoinUserDataText(query)
@ -3039,7 +3039,7 @@ namespace Emby.Server.Implementations.Data
return string.Empty; return string.Empty;
} }
return " ORDER BY " + string.Join(",", orderBy.Select(i => return " ORDER BY " + string.Join(',', orderBy.Select(i =>
{ {
var columnMap = MapOrderByField(i.Item1, query); var columnMap = MapOrderByField(i.Item1, query);
@ -3137,7 +3137,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var commandText = "select " var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -3203,7 +3203,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var commandText = "select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText(); var commandText = "select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid", "path" })) + GetFromText();
var whereClauses = GetWhereClauses(query, null); var whereClauses = GetWhereClauses(query, null);
if (whereClauses.Count != 0) if (whereClauses.Count != 0)
@ -3284,7 +3284,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
var commandText = "select " var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -3327,15 +3327,15 @@ namespace Emby.Server.Implementations.Data
if (EnableGroupByPresentationUniqueKey(query)) if (EnableGroupByPresentationUniqueKey(query))
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + GetFromText();
} }
else if (query.GroupBySeriesPresentationUniqueKey) else if (query.GroupBySeriesPresentationUniqueKey)
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
} }
else else
{ {
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText(); commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
} }
commandText += GetJoinUserDataText(query) commandText += GetJoinUserDataText(query)
@ -3596,7 +3596,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (excludeTypes.Length > 1) else if (excludeTypes.Length > 1)
{ {
var inClause = string.Join(",", excludeTypes.Select(i => "'" + i + "'")); var inClause = string.Join(',', excludeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type not in ({inClause})"); whereClauses.Add($"type not in ({inClause})");
} }
} }
@ -3607,7 +3607,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (includeTypes.Length > 1) else if (includeTypes.Length > 1)
{ {
var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'")); var inClause = string.Join(',', includeTypes.Select(i => "'" + i + "'"));
whereClauses.Add($"type in ({inClause})"); whereClauses.Add($"type in ({inClause})");
} }
@ -3618,7 +3618,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (query.ChannelIds.Count > 1) else if (query.ChannelIds.Count > 1)
{ {
var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); var inClause = string.Join(',', query.ChannelIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add($"ChannelId in ({inClause})"); whereClauses.Add($"ChannelId in ({inClause})");
} }
@ -4351,7 +4351,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (query.Years.Length > 1) else if (query.Years.Length > 1)
{ {
var val = string.Join(",", query.Years); var val = string.Join(',', query.Years);
whereClauses.Add("ProductionYear in (" + val + ")"); whereClauses.Add("ProductionYear in (" + val + ")");
} }
@ -4401,7 +4401,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (queryMediaTypes.Length > 1) else if (queryMediaTypes.Length > 1)
{ {
var val = string.Join(",", queryMediaTypes.Select(i => "'" + i + "'")); var val = string.Join(',', queryMediaTypes.Select(i => "'" + i + "'"));
whereClauses.Add("MediaType in (" + val + ")"); whereClauses.Add("MediaType in (" + val + ")");
} }
@ -4498,7 +4498,7 @@ namespace Emby.Server.Implementations.Data
var paramName = "@HasAnyProviderId" + index; var paramName = "@HasAnyProviderId" + index;
// this is a search for the placeholder // this is a search for the placeholder
hasProviderIds.Add("ProviderIds like " + paramName + ""); hasProviderIds.Add("ProviderIds like " + paramName);
// this replaces the placeholder with a value, here: %key=val% // this replaces the placeholder with a value, here: %key=val%
if (statement != null) if (statement != null)
@ -4549,7 +4549,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (enableItemsByName && includedItemByNameTypes.Count > 1) else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{ {
var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'")); var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))"); whereClauses.Add("(TopParentId=@TopParentId or Type in (" + itemByNameTypeVal + "))");
} }
else else
@ -4564,7 +4564,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (queryTopParentIds.Length > 1) else if (queryTopParentIds.Length > 1)
{ {
var val = string.Join(",", queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); var val = string.Join(',', queryTopParentIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
if (enableItemsByName && includedItemByNameTypes.Count == 1) if (enableItemsByName && includedItemByNameTypes.Count == 1)
{ {
@ -4576,7 +4576,7 @@ namespace Emby.Server.Implementations.Data
} }
else if (enableItemsByName && includedItemByNameTypes.Count > 1) else if (enableItemsByName && includedItemByNameTypes.Count > 1)
{ {
var itemByNameTypeVal = string.Join(",", includedItemByNameTypes.Select(i => "'" + i + "'")); var itemByNameTypeVal = string.Join(',', includedItemByNameTypes.Select(i => "'" + i + "'"));
whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))"); whereClauses.Add("(Type in (" + itemByNameTypeVal + ") or TopParentId in (" + val + "))");
} }
else else
@ -4597,7 +4597,7 @@ namespace Emby.Server.Implementations.Data
if (query.AncestorIds.Length > 1) if (query.AncestorIds.Length > 1)
{ {
var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'")); var inClause = string.Join(',', query.AncestorIds.Select(i => "'" + i.ToString("N", CultureInfo.InvariantCulture) + "'"));
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause)); whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
} }
@ -5148,7 +5148,7 @@ AND Type = @InternalPersonType)");
} }
else if (queryPersonTypes.Count > 1) else if (queryPersonTypes.Count > 1)
{ {
var val = string.Join(",", queryPersonTypes.Select(i => "'" + i + "'")); var val = string.Join(',', queryPersonTypes.Select(i => "'" + i + "'"));
whereClauses.Add("PersonType in (" + val + ")"); whereClauses.Add("PersonType in (" + val + ")");
} }
@ -5162,7 +5162,7 @@ AND Type = @InternalPersonType)");
} }
else if (queryExcludePersonTypes.Count > 1) else if (queryExcludePersonTypes.Count > 1)
{ {
var val = string.Join(",", queryExcludePersonTypes.Select(i => "'" + i + "'")); var val = string.Join(',', queryExcludePersonTypes.Select(i => "'" + i + "'"));
whereClauses.Add("PersonType not in (" + val + ")"); whereClauses.Add("PersonType not in (" + val + ")");
} }
@ -5308,19 +5308,19 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ? var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")"); ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
var commandText = "Select Value From ItemValues where " + typeClause; var commandText = "Select Value From ItemValues where " + typeClause;
if (withItemTypes.Count > 0) if (withItemTypes.Count > 0)
{ {
var typeString = string.Join(",", withItemTypes.Select(i => "'" + i + "'")); var typeString = string.Join(',', withItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))"; commandText += " AND ItemId In (select guid from typedbaseitems where type in (" + typeString + "))";
} }
if (excludeItemTypes.Count > 0) if (excludeItemTypes.Count > 0)
{ {
var typeString = string.Join(",", excludeItemTypes.Select(i => "'" + i + "'")); var typeString = string.Join(',', excludeItemTypes.Select(i => "'" + i + "'"));
commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))"; commandText += " AND ItemId not In (select guid from typedbaseitems where type in (" + typeString + "))";
} }
@ -5363,7 +5363,7 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ? var typeClause = itemValueTypes.Length == 1 ?
("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) : ("Type=" + itemValueTypes[0].ToString(CultureInfo.InvariantCulture)) :
("Type in (" + string.Join(",", itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")"); ("Type in (" + string.Join(',', itemValueTypes.Select(i => i.ToString(CultureInfo.InvariantCulture))) + ")");
InternalItemsQuery typeSubQuery = null; InternalItemsQuery typeSubQuery = null;
@ -5427,7 +5427,7 @@ AND Type = @InternalPersonType)");
columns = GetFinalColumnsToSelect(query, columns); columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select " var commandText = "select "
+ string.Join(",", columns) + string.Join(',', columns)
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query); + GetJoinUserDataText(query);
@ -5504,7 +5504,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount) if (query.EnableTotalRecordCount)
{ {
var countText = "select " var countText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query) + GetJoinUserDataText(query)
+ whereText; + whereText;
@ -5565,7 +5565,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount) if (query.EnableTotalRecordCount)
{ {
commandText = "select " commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" })) + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText() + GetFromText()
+ GetJoinUserDataText(query) + GetJoinUserDataText(query)
+ whereText; + whereText;

View file

@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction( connection.RunInTransaction(
db => db =>
{ {
db.ExecuteAll(string.Join(";", new[] { db.ExecuteAll(string.Join(';', new[] {
"create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)", "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",

View file

@ -249,7 +249,7 @@ namespace Emby.Server.Implementations.Dto
var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path); var activeRecording = liveTvManager.GetActiveRecordingInfo(item.Path);
if (activeRecording != null) if (activeRecording != null)
{ {
dto.Type = "Recording"; dto.Type = BaseItemKind.Recording;
dto.CanDownload = false; dto.CanDownload = false;
dto.RunTimeTicks = null; dto.RunTimeTicks = null;
@ -582,16 +582,22 @@ namespace Emby.Server.Implementations.Dto
{ {
baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary); baseItemPerson.PrimaryImageTag = GetTagAndFillBlurhash(dto, entity, ImageType.Primary);
baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture); baseItemPerson.Id = entity.Id.ToString("N", CultureInfo.InvariantCulture);
// Only add BlurHash for the person's image. if (dto.ImageBlurHashes != null)
baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
{ {
baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>(); // Only add BlurHash for the person's image.
foreach (var (imageId, blurHashValue) in blurHash) baseItemPerson.ImageBlurHashes = new Dictionary<ImageType, Dictionary<string, string>>();
foreach (var (imageType, blurHash) in dto.ImageBlurHashes)
{ {
if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase)) if (blurHash != null)
{ {
baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue; baseItemPerson.ImageBlurHashes[imageType] = new Dictionary<string, string>();
foreach (var (imageId, blurHashValue) in blurHash)
{
if (string.Equals(baseItemPerson.PrimaryImageTag, imageId, StringComparison.OrdinalIgnoreCase))
{
baseItemPerson.ImageBlurHashes[imageType][imageId] = blurHashValue;
}
}
} }
} }
} }
@ -898,7 +904,7 @@ namespace Emby.Server.Implementations.Dto
} }
} }
dto.Type = item.GetClientTypeName(); dto.Type = item.GetBaseItemKind();
if ((item.CommunityRating ?? 0) > 0) if ((item.CommunityRating ?? 0) > 0)
{ {
dto.CommunityRating = item.CommunityRating; dto.CommunityRating = item.CommunityRating;
@ -1151,7 +1157,7 @@ namespace Emby.Server.Implementations.Dto
if (episodeSeries != null) if (episodeSeries != null)
{ {
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary); dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, episodeSeries, ImageType.Primary);
if (!dto.ImageTags.ContainsKey(ImageType.Primary)) if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary))
{ {
AttachPrimaryImageAspectRatio(dto, episodeSeries); AttachPrimaryImageAspectRatio(dto, episodeSeries);
} }
@ -1201,7 +1207,7 @@ namespace Emby.Server.Implementations.Dto
if (series != null) if (series != null)
{ {
dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary); dto.SeriesPrimaryImageTag = GetTagAndFillBlurhash(dto, series, ImageType.Primary);
if (!dto.ImageTags.ContainsKey(ImageType.Primary)) if (dto.ImageTags == null || !dto.ImageTags.ContainsKey(ImageType.Primary))
{ {
AttachPrimaryImageAspectRatio(dto, series); AttachPrimaryImageAspectRatio(dto, series);
} }

View file

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.1" />
<PackageReference Include="sharpcompress" Version="0.26.0" /> <PackageReference Include="sharpcompress" Version="0.28.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" /> <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.2" /> <PackageReference Include="DotNet.Glob" Version="3.1.2" />
</ItemGroup> </ItemGroup>

View file

@ -5,6 +5,7 @@ using System.Buffers;
using System.IO.Pipelines; using System.IO.Pipelines;
using System.Net; using System.Net;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -138,7 +139,7 @@ namespace Emby.Server.Implementations.HttpServer
writer.Advance(bytesRead); writer.Advance(bytesRead);
// Make the data available to the PipeReader // Make the data available to the PipeReader
FlushResult flushResult = await writer.FlushAsync().ConfigureAwait(false); FlushResult flushResult = await writer.FlushAsync(cancellationToken).ConfigureAwait(false);
if (flushResult.IsCompleted) if (flushResult.IsCompleted)
{ {
// The PipeReader stopped reading // The PipeReader stopped reading
@ -181,32 +182,16 @@ namespace Emby.Server.Implementations.HttpServer
} }
WebSocketMessage<object>? stub; WebSocketMessage<object>? stub;
long bytesConsumed = 0;
try try
{ {
stub = DeserializeWebSocketMessage(buffer, out bytesConsumed);
if (buffer.IsSingleSegment)
{
stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buffer.FirstSpan, _jsonOptions);
}
else
{
var buf = ArrayPool<byte>.Shared.Rent(Convert.ToInt32(buffer.Length));
try
{
buffer.CopyTo(buf);
stub = JsonSerializer.Deserialize<WebSocketMessage<object>>(buf, _jsonOptions);
}
finally
{
ArrayPool<byte>.Shared.Return(buf);
}
}
} }
catch (JsonException ex) catch (JsonException ex)
{ {
// Tell the PipeReader how much of the buffer we have consumed // Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End); reader.AdvanceTo(buffer.End);
_logger.LogError(ex, "Error processing web socket message"); _logger.LogError(ex, "Error processing web socket message: {Data}", Encoding.UTF8.GetString(buffer));
return; return;
} }
@ -217,27 +202,34 @@ namespace Emby.Server.Implementations.HttpServer
} }
// Tell the PipeReader how much of the buffer we have consumed // Tell the PipeReader how much of the buffer we have consumed
reader.AdvanceTo(buffer.End); reader.AdvanceTo(buffer.GetPosition(bytesConsumed));
_logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub); _logger.LogDebug("WS {IP} received message: {@Message}", RemoteEndPoint, stub);
var info = new WebSocketMessageInfo if (stub.MessageType == SessionMessageType.KeepAlive)
{
MessageType = stub.MessageType,
Data = stub.Data?.ToString(), // Data can be null
Connection = this
};
if (info.MessageType == SessionMessageType.KeepAlive)
{ {
await SendKeepAliveResponse().ConfigureAwait(false); await SendKeepAliveResponse().ConfigureAwait(false);
} }
else else
{ {
await OnReceive(info).ConfigureAwait(false); await OnReceive(
new WebSocketMessageInfo
{
MessageType = stub.MessageType,
Data = stub.Data?.ToString(), // Data can be null
Connection = this
}).ConfigureAwait(false);
} }
} }
internal WebSocketMessage<object>? DeserializeWebSocketMessage(ReadOnlySequence<byte> bytes, out long bytesConsumed)
{
var jsonReader = new Utf8JsonReader(bytes);
var ret = JsonSerializer.Deserialize<WebSocketMessage<object>>(ref jsonReader, _jsonOptions);
bytesConsumed = jsonReader.BytesConsumed;
return ret;
}
private Task SendKeepAliveResponse() private Task SendKeepAliveResponse()
{ {
LastKeepAliveDate = DateTime.UtcNow; LastKeepAliveDate = DateTime.UtcNow;

View file

@ -582,9 +582,7 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetDirectories(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", GetEnumerationOptions(recursive)));
return ToMetadata(new DirectoryInfo(path).EnumerateDirectories("*", searchOption));
} }
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, bool recursive = false)
@ -594,16 +592,16 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetFiles(string path, IReadOnlyList<string> extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var enumerationOptions = GetEnumerationOptions(recursive);
// On linux and osx the search pattern is case sensitive // On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Count == 1) if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Count == 1)
{ {
return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], searchOption)); return ToMetadata(new DirectoryInfo(path).EnumerateFiles("*" + extensions[0], enumerationOptions));
} }
var files = new DirectoryInfo(path).EnumerateFiles("*", searchOption); var files = new DirectoryInfo(path).EnumerateFiles("*", enumerationOptions);
if (extensions != null && extensions.Count > 0) if (extensions != null && extensions.Count > 0)
{ {
@ -625,10 +623,10 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false) public virtual IEnumerable<FileSystemMetadata> GetFileSystemEntries(string path, bool recursive = false)
{ {
var directoryInfo = new DirectoryInfo(path); var directoryInfo = new DirectoryInfo(path);
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var enumerationOptions = GetEnumerationOptions(recursive);
return ToMetadata(directoryInfo.EnumerateDirectories("*", searchOption)) return ToMetadata(directoryInfo.EnumerateDirectories("*", enumerationOptions))
.Concat(ToMetadata(directoryInfo.EnumerateFiles("*", searchOption))); .Concat(ToMetadata(directoryInfo.EnumerateFiles("*", enumerationOptions)));
} }
private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos) private IEnumerable<FileSystemMetadata> ToMetadata(IEnumerable<FileSystemInfo> infos)
@ -638,8 +636,7 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false) public virtual IEnumerable<string> GetDirectoryPaths(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; return Directory.EnumerateDirectories(path, "*", GetEnumerationOptions(recursive));
return Directory.EnumerateDirectories(path, "*", searchOption);
} }
public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false) public virtual IEnumerable<string> GetFilePaths(string path, bool recursive = false)
@ -649,16 +646,16 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false) public virtual IEnumerable<string> GetFilePaths(string path, string[] extensions, bool enableCaseSensitiveExtensions, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; var enumerationOptions = GetEnumerationOptions(recursive);
// On linux and osx the search pattern is case sensitive // On linux and osx the search pattern is case sensitive
// If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method // If we're OK with case-sensitivity, and we're only filtering for one extension, then use the native method
if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1) if ((enableCaseSensitiveExtensions || _isEnvironmentCaseInsensitive) && extensions != null && extensions.Length == 1)
{ {
return Directory.EnumerateFiles(path, "*" + extensions[0], searchOption); return Directory.EnumerateFiles(path, "*" + extensions[0], enumerationOptions);
} }
var files = Directory.EnumerateFiles(path, "*", searchOption); var files = Directory.EnumerateFiles(path, "*", enumerationOptions);
if (extensions != null && extensions.Length > 0) if (extensions != null && extensions.Length > 0)
{ {
@ -679,8 +676,18 @@ namespace Emby.Server.Implementations.IO
public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false) public virtual IEnumerable<string> GetFileSystemEntryPaths(string path, bool recursive = false)
{ {
var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; return Directory.EnumerateFileSystemEntries(path, "*", GetEnumerationOptions(recursive));
return Directory.EnumerateFileSystemEntries(path, "*", searchOption); }
private EnumerationOptions GetEnumerationOptions(bool recursive)
{
return new EnumerationOptions
{
RecurseSubdirectories = recursive,
IgnoreInaccessible = true,
// Don't skip any files.
AttributesToSkip = 0
};
} }
private static void RunProcess(string path, string args, string workingDirectory) private static void RunProcess(string path, string args, string workingDirectory)

View file

@ -515,7 +515,7 @@ namespace Emby.Server.Implementations.Library
} }
// TODO: @bond Fix // TODO: @bond Fix
var json = JsonSerializer.Serialize(mediaSource, _jsonOptions); var json = JsonSerializer.SerializeToUtf8Bytes(mediaSource, _jsonOptions);
_logger.LogInformation("Live stream opened: " + json); _logger.LogInformation("Live stream opened: " + json);
var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions); var clone = JsonSerializer.Deserialize<MediaSourceInfo>(json, _jsonOptions);

View file

@ -79,11 +79,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return new MusicArtist(); return new MusicArtist();
} }
if (_config.Configuration.EnableSimpleArtistDetection)
{
return null;
}
// Avoid mis-identifying top folders // Avoid mis-identifying top folders
if (args.Parent.IsRoot) if (args.Parent.IsRoot)
{ {

View file

@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
} }
// It's a directory-based playlist if the directory contains a playlist file // It's a directory-based playlist if the directory contains a playlist file
var filePaths = Directory.EnumerateFiles(args.Path); var filePaths = Directory.EnumerateFiles(args.Path, "*", new EnumerationOptions { IgnoreInaccessible = true });
if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase))) if (filePaths.Any(f => f.EndsWith(PlaylistXmlSaver.DefaultPlaylistFilename, StringComparison.OrdinalIgnoreCase)))
{ {
return new Playlist return new Playlist

View file

@ -47,11 +47,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
try try
{ {
var jsonString = File.ReadAllText(_dataPath, Encoding.UTF8); var bytes = File.ReadAllBytes(_dataPath);
_items = JsonSerializer.Deserialize<T[]>(jsonString, _jsonOptions); _items = JsonSerializer.Deserialize<T[]>(bytes, _jsonOptions);
return; return;
} }
catch (Exception ex) catch (JsonException ex)
{ {
Logger.LogError(ex, "Error deserializing {Path}", _dataPath); Logger.LogError(ex, "Error deserializing {Path}", _dataPath);
} }

View file

@ -10,6 +10,7 @@ using System.Net.Http;
using System.Net.Mime; using System.Net.Mime;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common; using MediaBrowser.Common;
@ -35,8 +36,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider; private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>(); private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private DateTime _lastErrorResponse;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions(); private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private DateTime _lastErrorResponse;
public SchedulesDirect( public SchedulesDirect(
ILogger<SchedulesDirect> logger, ILogger<SchedulesDirect> logger,
@ -111,7 +112,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
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); await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions).ConfigureAwait(false); var dailySchedules = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.Day>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId); _logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs"); using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
@ -122,12 +123,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
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); await using var innerResponseStream = await innerResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions).ConfigureAwait(false); var programDetails = await JsonSerializer.DeserializeAsync<List<ScheduleDirect.ProgramDetails>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
var programDict = programDetails.ToDictionary(p => p.programID, y => y); var programDict = programDetails.ToDictionary(p => p.programID, y => y);
var programIdsWithImages = var programIdsWithImages = programDetails
programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID) .Where(p => p.hasImageArtwork).Select(p => p.programID)
.ToList(); .ToList();
var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false); var images = await GetImageForPrograms(info, programIdsWithImages, cancellationToken).ConfigureAwait(false);
@ -182,8 +183,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private static int GetSizeOrder(ScheduleDirect.ImageData image) private static int GetSizeOrder(ScheduleDirect.ImageData image)
{ {
if (!string.IsNullOrWhiteSpace(image.height) if (int.TryParse(image.height, out int value))
&& int.TryParse(image.height, out int value))
{ {
return value; return value;
} }
@ -704,7 +704,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode(); httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
using var response = httpResponse.Content; using var response = httpResponse.Content;
var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions).ConfigureAwait(false); var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Lineups>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase)); return root.lineups.Any(i => string.Equals(info.ListingsId, i.lineup, StringComparison.OrdinalIgnoreCase));
} }
@ -776,7 +776,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
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); await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions).ConfigureAwait(false); var root = await JsonSerializer.DeserializeAsync<ScheduleDirect.Channel>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count); _logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Mapping Stations to Channel"); _logger.LogInformation("Mapping Stations to Channel");

View file

@ -2239,7 +2239,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true) public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{ {
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info)); info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@ -2283,7 +2283,7 @@ namespace Emby.Server.Implementations.LiveTv
{ {
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider // Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider // ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info)); info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.SerializeToUtf8Bytes(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase)); var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));

View file

@ -26,7 +26,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
public LegacyHdHomerunChannelCommands(string url) public LegacyHdHomerunChannelCommands(string url)
{ {
// parse url for channel and program // parse url for channel and program
var regExp = new Regex(@"\/ch(\d+)-?(\d*)"); var regExp = new Regex(@"\/ch([0-9]+)-?([0-9]*)");
var match = regExp.Match(url); var match = regExp.Match(url);
if (match.Success) if (match.Success)
{ {

View file

@ -92,7 +92,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
try try
{ {
await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort).ConfigureAwait(false); await tcpClient.ConnectAsync(remoteAddress, HdHomerunManager.HdHomeRunPort, openCancellationToken).ConfigureAwait(false);
localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address; localAddress = ((IPEndPoint)tcpClient.Client.LocalEndPoint).Address;
tcpClient.Close(); tcpClient.Close();
} }

View file

@ -155,7 +155,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
if (channelIdValues.Count > 0) if (channelIdValues.Count > 0)
{ {
channel.Id = string.Join("_", channelIdValues); channel.Id = string.Join('_', channelIdValues);
} }
return channel; return channel;

View file

@ -159,7 +159,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
EnableStreamSharing = false; EnableStreamSharing = false;
await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false); await DeleteTempFiles(new List<string> { TempFilePath }).ConfigureAwait(false);
}); }, CancellationToken.None);
} }
private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource) private void Resolve(TaskCompletionSource<bool> openTaskCompletionSource)

View file

@ -8,7 +8,7 @@
"CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}", "CameraImageUploadedFrom": "Нова снимка от камера беше качена от {0}",
"Channels": "Канали", "Channels": "Канали",
"ChapterNameValue": "Глава {0}", "ChapterNameValue": "Глава {0}",
"Collections": "Колекции", "Collections": "Поредици",
"DeviceOfflineWithName": "{0} се разкачи", "DeviceOfflineWithName": "{0} се разкачи",
"DeviceOnlineWithName": "{0} е свързан", "DeviceOnlineWithName": "{0} е свързан",
"FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}", "FailedLoginAttemptWithUserName": "Неуспешен опит за влизане от {0}",
@ -55,26 +55,26 @@
"NotificationOptionPluginInstalled": "Приставката е инсталирана", "NotificationOptionPluginInstalled": "Приставката е инсталирана",
"NotificationOptionPluginUninstalled": "Приставката е деинсталирана", "NotificationOptionPluginUninstalled": "Приставката е деинсталирана",
"NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано", "NotificationOptionPluginUpdateInstalled": "Обновлението на приставката е инсталирано",
"NotificationOptionServerRestartRequired": "Нужно е повторно пускане на сървъра", "NotificationOptionServerRestartRequired": "Сървърът трябва да се рестартира",
"NotificationOptionTaskFailed": "Грешка в планирана задача", "NotificationOptionTaskFailed": "Грешка в планирана задача",
"NotificationOptionUserLockedOut": "Потребителя е заключен", "NotificationOptionUserLockedOut": "Потребителят е заключен",
"NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна", "NotificationOptionVideoPlayback": "Възпроизвеждането на видео започна",
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки", "Photos": "Снимки",
"Playlists": "Списъци", "Playlists": "Списъци",
"Plugin": "Приставка", "Plugin": "Приставка",
"PluginInstalledWithName": "{0} е инсталирано", "PluginInstalledWithName": "{0} е инсталиранa",
"PluginUninstalledWithName": "{0} е деинсталирано", "PluginUninstalledWithName": "{0} е деинсталиранa",
"PluginUpdatedWithName": "{0} е обновено", "PluginUpdatedWithName": "{0} е обновенa",
"ProviderValue": "Доставчик: {0}", "ProviderValue": "Доставчик: {0}",
"ScheduledTaskFailedWithName": "{0} се провали", "ScheduledTaskFailedWithName": "{0} се провали",
"ScheduledTaskStartedWithName": "{0} започна", "ScheduledTaskStartedWithName": "{0} започна",
"ServerNameNeedsToBeRestarted": "{0} е нужно да се рестартира", "ServerNameNeedsToBeRestarted": "{0} трябва да се рестартира",
"Shows": "Сериали", "Shows": "Сериали",
"Songs": "Песни", "Songs": "Песни",
"StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.", "StartupEmbyServerIsLoading": "Сървърът зарежда. Моля, опитайте отново след малко.",
"SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}", "SubtitleDownloadFailureForItem": "Неуспешно изтегляне на субтитри за {0}",
"SubtitleDownloadFailureFromForItem": "Поднадписите за {1} от {0} не можаха да се изтеглят", "SubtitleDownloadFailureFromForItem": "Субтитрите за {1} от {0} не можаха да бъдат изтеглени",
"Sync": "Синхронизиране", "Sync": "Синхронизиране",
"System": "Система", "System": "Система",
"TvShows": "Телевизионни сериали", "TvShows": "Телевизионни сериали",
@ -92,12 +92,12 @@
"ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека", "ValueHasBeenAddedToLibrary": "{0} беше добавен във Вашата библиотека",
"ValueSpecialEpisodeName": "Специални - {0}", "ValueSpecialEpisodeName": "Специални - {0}",
"VersionNumber": "Версия {0}", "VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи поднадписи, на база конфигурацията за мета-данни.", "TaskDownloadMissingSubtitlesDescription": "Търси Интернет за липсващи субтитри, на база конфигурацията за мета-данни.",
"TaskDownloadMissingSubtitles": "Изтегляне на липсващи поднадписи", "TaskDownloadMissingSubtitles": "Изтегляне на липсващи субтитри",
"TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.", "TaskRefreshChannelsDescription": "Обновява информацията за интернет канала.",
"TaskRefreshChannels": "Обновяване на Канали", "TaskRefreshChannels": "Обновяване на Канали",
"TaskCleanTranscodeDescription": "Изтрива прекодирани файлове по-стари от един ден.", "TaskCleanTranscodeDescription": "Изтрива транскодирани файлове по-стари от един ден.",
"TaskCleanTranscode": "Изчиства директорията за прекодиране", "TaskCleanTranscode": "Изчиства директорията за транскодиране",
"TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.", "TaskUpdatePluginsDescription": "Изтегля и инсталира актуализации за добавките, които са настроени за автоматична актуализация.",
"TaskUpdatePlugins": "Актуализира добавките", "TaskUpdatePlugins": "Актуализира добавките",
"TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.", "TaskRefreshPeopleDescription": "Актуализира мета-данните за артистите и режисьорите за Вашата медийна библиотека.",
@ -113,5 +113,8 @@
"TasksChannelsCategory": "Интернет Канали", "TasksChannelsCategory": "Интернет Канали",
"TasksApplicationCategory": "Приложение", "TasksApplicationCategory": "Приложение",
"TasksLibraryCategory": "Библиотека", "TasksLibraryCategory": "Библиотека",
"TasksMaintenanceCategory": "Поддръжка" "TasksMaintenanceCategory": "Поддръжка",
"Undefined": "Неопределено",
"Forced": "Принудително",
"Default": "По подразбиране"
} }

View file

@ -18,10 +18,10 @@
"HeaderAlbumArtists": "Artistes del Àlbum", "HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderContinueWatching": "Continua Veient", "HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteAlbums": "Àlbums Preferits",
"HeaderFavoriteArtists": "Artistes Preferits", "HeaderFavoriteArtists": "Artistes Predilectes",
"HeaderFavoriteEpisodes": "Episodis Preferits", "HeaderFavoriteEpisodes": "Episodis Predilectes",
"HeaderFavoriteShows": "Programes Preferits", "HeaderFavoriteShows": "Programes Predilectes",
"HeaderFavoriteSongs": "Cançons Preferides", "HeaderFavoriteSongs": "Cançons Predilectes",
"HeaderLiveTV": "TV en Directe", "HeaderLiveTV": "TV en Directe",
"HeaderNextUp": "A continuació", "HeaderNextUp": "A continuació",
"HeaderRecordingGroups": "Grups d'Enregistrament", "HeaderRecordingGroups": "Grups d'Enregistrament",
@ -36,7 +36,7 @@
"MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}", "MessageApplicationUpdatedTo": "El Servidor de Jellyfin ha estat actualitzat a {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada", "MessageNamedServerConfigurationUpdatedWithValue": "La secció {0} de la configuració del servidor ha estat actualitzada",
"MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor", "MessageServerConfigurationUpdated": "S'ha actualitzat la configuració del servidor",
"MixedContent": "Contingut mesclat", "MixedContent": "Contingut barrejat",
"Movies": "Pel·lícules", "Movies": "Pel·lícules",
"Music": "Música", "Music": "Música",
"MusicVideos": "Vídeos musicals", "MusicVideos": "Vídeos musicals",
@ -76,7 +76,7 @@
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}", "SubtitleDownloadFailureFromForItem": "Els subtítols no s'han pogut baixar de {0} per {1}",
"Sync": "Sincronitzar", "Sync": "Sincronitzar",
"System": "System", "System": "Sistema",
"TvShows": "Espectacles de TV", "TvShows": "Espectacles de TV",
"User": "User", "User": "User",
"UserCreatedWithName": "S'ha creat l'usuari {0}", "UserCreatedWithName": "S'ha creat l'usuari {0}",
@ -113,5 +113,10 @@
"TasksChannelsCategory": "Canals d'internet", "TasksChannelsCategory": "Canals d'internet",
"TasksApplicationCategory": "Aplicació", "TasksApplicationCategory": "Aplicació",
"TasksLibraryCategory": "Biblioteca", "TasksLibraryCategory": "Biblioteca",
"TasksMaintenanceCategory": "Manteniment" "TasksMaintenanceCategory": "Manteniment",
"TaskCleanActivityLogDescription": "Eliminat entrades del registre d'activitats mes antigues que l'antiguitat configurada.",
"TaskCleanActivityLog": "Buidar Registre d'Activitat",
"Undefined": "Indefinit",
"Forced": "Forçat",
"Default": "Defecto"
} }

View file

@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}", "AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung", "Application": "Anwendung",
"Artists": "Interpreten", "Artists": "Interpreten",
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet", "AuthenticationSucceededWithUserName": "{0} wurde angemeldet",
"Books": "Bücher", "Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Kamerafoto wurde von {0} hochgeladen", "CameraImageUploadedFrom": "Ein neues Kamerafoto wurde von {0} hochgeladen",
"Channels": "Kanäle", "Channels": "Kanäle",
@ -94,22 +94,22 @@
"VersionNumber": "Version {0}", "VersionNumber": "Version {0}",
"TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.", "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.",
"TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter", "TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter",
"TaskRefreshChannelsDescription": "Erneuere Internet Kanal Informationen.", "TaskRefreshChannelsDescription": "Aktualisiere Internet Kanal Informationen.",
"TaskRefreshChannels": "Erneuere Kanäle", "TaskRefreshChannels": "Aktualisiere Kanäle",
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.", "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, welche älter als einen Tag sind.",
"TaskCleanTranscode": "Lösche Transkodier Pfad", "TaskCleanTranscode": "Lösche Transkodier-Pfad",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.", "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.",
"TaskUpdatePlugins": "Update Plugins", "TaskUpdatePlugins": "Aktualisiere Plugins",
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.", "TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
"TaskRefreshPeople": "Erneuere Schauspieler", "TaskRefreshPeople": "Aktualisiere Schauspieler",
"TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.", "TaskCleanLogsDescription": "Lösche Log Dateien, die älter als {0} Tage sind.",
"TaskCleanLogs": "Lösche Log Pfad", "TaskCleanLogs": "Lösche Log-Verzeichnis",
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.", "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
"TaskRefreshLibrary": "Scanne Medien-Bibliothek", "TaskRefreshLibrary": "Scanne Medien-Bibliothek",
"TaskRefreshChapterImagesDescription": "Kreiert Vorschaubilder für Videos welche Kapitel haben.", "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, welche Kapitel besitzen.",
"TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder", "TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder",
"TaskCleanCacheDescription": "Löscht Zwischenspeicherdatein die nicht länger von System gebraucht werden.", "TaskCleanCacheDescription": "Löscht nicht mehr benötigte Zwischenspeicherdateien.",
"TaskCleanCache": "Leere Cache Pfad", "TaskCleanCache": "Leere Zwischenspeicher",
"TasksChannelsCategory": "Internet Kanäle", "TasksChannelsCategory": "Internet Kanäle",
"TasksApplicationCategory": "Anwendung", "TasksApplicationCategory": "Anwendung",
"TasksLibraryCategory": "Bibliothek", "TasksLibraryCategory": "Bibliothek",

View file

@ -0,0 +1,26 @@
{
"NotificationOptionInstallationFailed": "Instalada fiasko",
"NotificationOptionAudioPlaybackStopped": "Sono de ludado haltis",
"NotificationOptionAudioPlayback": "Ludado de sono startis",
"NameSeasonUnknown": "Sezono Nekonata",
"NameSeasonNumber": "Sezono {0}",
"NameInstallFailed": "{0} instalado fiaskis",
"Music": "Muziko",
"Movies": "Filmoj",
"ItemRemovedWithName": "{0} forigis el la biblioteko",
"ItemAddedWithName": "{0} aldonis al la biblioteko",
"HeaderLiveTV": "Viva Televido",
"HeaderContinueWatching": "Daŭrigi Spektado",
"HeaderAlbumArtists": "Artistoj de Albumo",
"Folders": "Dosierujoj",
"DeviceOnlineWithName": "{0} estas konektita",
"Default": "Defaŭlte",
"Collections": "Kolektoj",
"ChapterNameValue": "Ĉapitro {0}",
"Channels": "Kanaloj",
"Books": "Libroj",
"Artists": "Artistoj",
"Application": "Aplikaĵo",
"AppDeviceValues": "Aplikaĵo: {0}, Aparato: {1}",
"Albums": "Albumoj"
}

View file

@ -116,5 +116,6 @@
"TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.", "TaskCleanActivityLogDescription": "Elimina las entradas del registro de actividad anteriores al periodo configurado.",
"TaskCleanActivityLog": "Limpiar Registro de Actividades", "TaskCleanActivityLog": "Limpiar Registro de Actividades",
"Undefined": "Sin definir", "Undefined": "Sin definir",
"Forced": "Forzado" "Forced": "Forzado",
"Default": "Por Defecto"
} }

View file

@ -49,7 +49,7 @@
"NotificationOptionAudioPlayback": "پخش صدا آغاز شد", "NotificationOptionAudioPlayback": "پخش صدا آغاز شد",
"NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد", "NotificationOptionAudioPlaybackStopped": "پخش صدا متوقف شد",
"NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد", "NotificationOptionCameraImageUploaded": "تصاویر دوربین آپلود شد",
"NotificationOptionInstallationFailed": "نصب شکست خورد", "NotificationOptionInstallationFailed": "نصب ناموفق",
"NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد", "NotificationOptionNewLibraryContent": "محتوای جدید افزوده شد",
"NotificationOptionPluginError": "خرابی افزونه", "NotificationOptionPluginError": "خرابی افزونه",
"NotificationOptionPluginInstalled": "افزونه نصب شد", "NotificationOptionPluginInstalled": "افزونه نصب شد",
@ -115,5 +115,8 @@
"TasksLibraryCategory": "کتابخانه", "TasksLibraryCategory": "کتابخانه",
"TasksMaintenanceCategory": "تعمیر", "TasksMaintenanceCategory": "تعمیر",
"Forced": "اجباری", "Forced": "اجباری",
"Default": "پیشفرض" "Default": "پیشفرض",
"TaskCleanActivityLogDescription": "ورودی‌های قدیمی‌تر از سن تنظیم شده در سیاهه فعالیت را حذف می‌کند.",
"TaskCleanActivityLog": "پاکسازی سیاهه فعالیت",
"Undefined": "تعریف نشده"
} }

View file

@ -1,121 +1,121 @@
{ {
"HeaderLiveTV": "Live-TV", "HeaderLiveTV": "Live TV",
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.", "NewVersionIsAvailable": "Uusi versio Jellyfin-palvelimesta on ladattavissa.",
"NameSeasonUnknown": "Tuntematon kausi", "NameSeasonUnknown": "Tuntematon kausi",
"NameSeasonNumber": "Kausi {0}", "NameSeasonNumber": "Kausi {0}",
"NameInstallFailed": "{0} asennus epäonnistui", "NameInstallFailed": "{0} asennus epäonnistui",
"MusicVideos": "Musiikkivideot", "MusicVideos": "Musiikkivideot",
"Music": "Musiikki", "Music": "Musiikki",
"Movies": "Elokuvat", "Movies": "Elokuvat",
"MixedContent": "Sekoitettu sisältö", "MixedContent": "Sekalainen sisältö",
"MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty", "MessageServerConfigurationUpdated": "Palvelimen asetukset on päivitetty",
"MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusryhmä {0} on päivitetty", "MessageNamedServerConfigurationUpdatedWithValue": "Palvelimen asetusten osio {0} on päivitetty",
"MessageApplicationUpdatedTo": "Jellyfin palvelin on päivitetty versioon {0}", "MessageApplicationUpdatedTo": "Jellyfin-palvelin on päivitetty versioon {0}",
"MessageApplicationUpdated": "Jellyfin palvelin on päivitetty", "MessageApplicationUpdated": "Jellyfin-palvelin on päivitetty",
"Latest": "Uusimmat", "Latest": "Viimeisimmät",
"LabelRunningTimeValue": "Toiston kesto: {0}", "LabelRunningTimeValue": "Kesto: {0}",
"LabelIpAddressValue": "IP-osoite: {0}", "LabelIpAddressValue": "IP-osoite: {0}",
"ItemRemovedWithName": "{0} poistettiin kirjastosta", "ItemRemovedWithName": "{0} poistettiin kirjastosta",
"ItemAddedWithName": "{0} lisättiin kirjastoon", "ItemAddedWithName": "{0} lisättiin kirjastoon",
"Inherit": "Periytyä", "Inherit": "Peri",
"HomeVideos": "Kotivideot", "HomeVideos": "Kotivideot",
"HeaderRecordingGroups": "Tallennusryhmät", "HeaderRecordingGroups": "Tallennusryhmät",
"HeaderNextUp": "Seuraavaksi", "HeaderNextUp": "Seuraavaksi",
"HeaderFavoriteSongs": "Suosikkikappaleet", "HeaderFavoriteSongs": "Suosikkikappaleet",
"HeaderFavoriteShows": "Suosikkisarjat", "HeaderFavoriteShows": "Suosikkisarjat",
"HeaderFavoriteEpisodes": "Suosikkijaksot", "HeaderFavoriteEpisodes": "Suosikkijaksot",
"HeaderFavoriteArtists": "Suosikkiartistit", "HeaderFavoriteArtists": "Suosikkiesittäjät",
"HeaderFavoriteAlbums": "Suosikkialbumit", "HeaderFavoriteAlbums": "Suosikkialbumit",
"HeaderContinueWatching": "Jatka katsomista", "HeaderContinueWatching": "Jatka katselua",
"HeaderAlbumArtists": "Albumin artistit", "HeaderAlbumArtists": "Albumin esittäjät",
"Genres": "Tyylilajit", "Genres": "Tyylilajit",
"Folders": "Kansiot", "Folders": "Kansiot",
"Favorites": "Suosikit", "Favorites": "Suosikit",
"FailedLoginAttemptWithUserName": "Kirjautuminen epäonnistui kohteesta {0}", "FailedLoginAttemptWithUserName": "Epäonnistunut kirjautumisyritys lähteestä \"{0}\"",
"DeviceOnlineWithName": "{0} on yhdistetty", "DeviceOnlineWithName": "{0} on yhdistetty",
"DeviceOfflineWithName": "{0} yhteys on katkaistu", "DeviceOfflineWithName": "{0} on katkaissut yhteyden",
"Collections": "Kokoelmat", "Collections": "Kokoelmat",
"ChapterNameValue": "Jakso: {0}", "ChapterNameValue": "Kappale {0}",
"Channels": "Kanavat", "Channels": "Kanavat",
"CameraImageUploadedFrom": "Uusi kamerakuva on ladattu {0}", "CameraImageUploadedFrom": "Uusi kameran kuva on sirretty lähteestä {0}",
"Books": "Kirjat", "Books": "Kirjat",
"AuthenticationSucceededWithUserName": "{0} todennus onnistui", "AuthenticationSucceededWithUserName": "{0} on todennettu",
"Artists": "Artistit", "Artists": "Esittäjät",
"Application": "Sovellus", "Application": "Sovellus",
"AppDeviceValues": "Sovellus: {0}, Laite: {1}", "AppDeviceValues": "Sovellus: {0}, Laite: {1}",
"Albums": "Albumit", "Albums": "Albumit",
"User": "Käyttäjä", "User": "Käyttäjä",
"System": "Järjestelmä", "System": "Järjestelmä",
"ScheduledTaskFailedWithName": "{0} epäonnistui", "ScheduledTaskFailedWithName": "{0} epäonnistui",
"PluginUpdatedWithName": "{0} päivitetty", "PluginUpdatedWithName": "{0} päivitettiin",
"PluginInstalledWithName": "{0} asennettu", "PluginInstalledWithName": "{0} asennettiin",
"Photos": "Kuvat", "Photos": "Valokuvat",
"ScheduledTaskStartedWithName": "{0} aloitettu", "ScheduledTaskStartedWithName": "\"{0}\" käynnistetty",
"PluginUninstalledWithName": "{0} poistettu", "PluginUninstalledWithName": "{0} poistettiin",
"Playlists": "Soittolistat", "Playlists": "Soittolistat",
"VersionNumber": "Versio {0}", "VersionNumber": "Versio {0}",
"ValueSpecialEpisodeName": "Erikois - {0}", "ValueSpecialEpisodeName": "Erikoisjakso - {0}",
"ValueHasBeenAddedToLibrary": "{0} lisättiin mediakirjastoon", "ValueHasBeenAddedToLibrary": "\"{0}\" on lisätty mediakirjastoon",
"UserStoppedPlayingItemWithValues": "{0} toistaminen valmistui {1} laitteella {2}", "UserStoppedPlayingItemWithValues": "{0} lopetti kohteen \"{1}\" toiston sijainnissa \"{2}\"",
"UserStartedPlayingItemWithValues": "{0} toistaa {1} laitteella {2}", "UserStartedPlayingItemWithValues": "{0} toistaa kohdetta \"{1}\" sijainnissa \"{2}\"",
"UserPolicyUpdatedWithName": "Käyttöoikeudet päivitetty käyttäjälle {0}", "UserPolicyUpdatedWithName": "Käyttäjän {0} käyttöoikeudet on päivitetty",
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}", "UserPasswordChangedWithName": "Käyttäjän {0} salasana on vaihdettu",
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}", "UserOnlineFromDevice": "{0} on yhdistänyt sijainnista \"{1}\"",
"UserOfflineFromDevice": "{0} yhteys katkaistu kohteesta {1}", "UserOfflineFromDevice": "{0} on katkaissut yhteyden sijainnista \"{1}\"",
"UserLockedOutWithName": "Käyttäjä {0} lukittu", "UserLockedOutWithName": "Käyttäjä {0} on lukittu",
"UserDownloadingItemWithValues": "{0} lataa {1}", "UserDownloadingItemWithValues": "{0} lataa kohdetta \"{1}\"",
"UserDeletedWithName": "Käyttäjä {0} poistettu", "UserDeletedWithName": "Käyttäjä {0} on poistettu",
"UserCreatedWithName": "Käyttäjä {0} luotu", "UserCreatedWithName": "Käyttäjä {0} on luotu",
"TvShows": "TV-ohjelmat", "TvShows": "Sarjat",
"Sync": "Synkronoi", "Sync": "Synkronointi",
"SubtitleDownloadFailureFromForItem": "Tekstitystä ei voitu ladata osoitteesta {0} kohteelle {1}", "SubtitleDownloadFailureFromForItem": "Tekstityksen lataus lähteestä \"{0}\" kohteelle \"{1}\" epäonnistui",
"StartupEmbyServerIsLoading": "Jellyfin palvelin latautuu. Yritä hetken kuluttua uudelleen.", "StartupEmbyServerIsLoading": "Jellyfin-palvelin latautuu. Yritä hetken kuluttua uudelleen.",
"Songs": "Kappaleet", "Songs": "Kappaleet",
"Shows": "Ohjelmat", "Shows": "Sarjat",
"ServerNameNeedsToBeRestarted": "{0} on käynnistettävä uudelleen", "ServerNameNeedsToBeRestarted": "\"{0}\" on käynnistettävä uudelleen",
"ProviderValue": "Tarjoaja: {0}", "ProviderValue": "Lähde: {0}",
"Plugin": "Liitännäinen", "Plugin": "Laajennus",
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty", "NotificationOptionVideoPlaybackStopped": "Videon toisto lopetettu",
"NotificationOptionVideoPlayback": "Videota toistetaan", "NotificationOptionVideoPlayback": "Videon toisto aloitettu",
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos", "NotificationOptionUserLockedOut": "Käyttäjä on lukittu",
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui", "NotificationOptionTaskFailed": "Ajoitettu tehtävä epäonnistui",
"NotificationOptionServerRestartRequired": "Palvelin on käynnistettävä uudelleen", "NotificationOptionServerRestartRequired": "Tarvitaan palvelimen uudelleenkäynnistys",
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty", "NotificationOptionPluginUpdateInstalled": "Laajennus on päivitetty",
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu", "NotificationOptionPluginUninstalled": "Laajennus on poistettu",
"NotificationOptionPluginInstalled": "Liitännäinen asennettu", "NotificationOptionPluginInstalled": "Laajennus on asennettu",
"NotificationOptionPluginError": "Ongelma liitännäisessä", "NotificationOptionPluginError": "Laajennuksen virhe",
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty", "NotificationOptionNewLibraryContent": "Sisältöä on lisätty",
"NotificationOptionInstallationFailed": "Asennus epäonnistui", "NotificationOptionInstallationFailed": "Asennus epäonnistui",
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu", "NotificationOptionCameraImageUploaded": "Kameran kuva on tallennettu",
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu", "NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
"NotificationOptionAudioPlayback": "Toistetaan ääntä", "NotificationOptionAudioPlayback": "Äänen toisto aloitettu",
"NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettu", "NotificationOptionApplicationUpdateInstalled": "Sovelluspäivitys asennettiin",
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla", "NotificationOptionApplicationUpdateAvailable": "Sovelluspäivitys on saatavilla",
"TasksMaintenanceCategory": "Ylläpito", "TasksMaintenanceCategory": "Ylläpito",
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.", "TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä määritettyjen metatietoasetusten mukaisesti.",
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset", "TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.", "TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
"TaskRefreshChannels": "Päivitä kanavat", "TaskRefreshChannels": "Päivitä kanavat",
"TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.", "TaskCleanTranscodeDescription": "Poistaa päivää vanhemmat transkoodaustiedostot.",
"TaskCleanTranscode": "Puhdista transkoodaushakemisto", "TaskCleanTranscode": "Puhdista transkoodauskansio",
"TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.", "TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset laajennuksille, jotka on määritetty päivittymään automaattisesti.",
"TaskUpdatePlugins": "Päivitä liitännäiset", "TaskUpdatePlugins": "Päivitä laajennukset",
"TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.", "TaskRefreshPeopleDescription": "Päivittää mediakirjaston näyttelijöiden ja ohjaajien metatiedot.",
"TaskRefreshPeople": "Päivitä henkilöt", "TaskRefreshPeople": "Päivitä henkilöt",
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.", "TaskCleanLogsDescription": "Poistaa {0} päivää vanhemmat lokitiedostot.",
"TaskCleanLogs": "Puhdista lokihakemisto", "TaskCleanLogs": "Siivoa lokikansio",
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uudet tiedostot ja päivittää metatiedot.", "TaskRefreshLibraryDescription": "Tarkastaa mediakirjastosi sisällön uusien tiedostojen varalta ja päivittää metatiedot.",
"TaskRefreshLibrary": "Skannaa mediakirjasto", "TaskRefreshLibrary": "Päivitä mediakirjasto",
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on jaksoja.", "TaskRefreshChapterImagesDescription": "Luo esikatselukuvat videoille, jotka sisältävät kappalejaon.",
"TaskRefreshChapterImages": "Pura jakson kuvat", "TaskRefreshChapterImages": "Pura kappalejaon kuvat",
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.", "TaskCleanCacheDescription": "Poistaa tarpeettomiksi jääneet väliaikaistiedostot.",
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto", "TaskCleanCache": "Tyhjennä välimuistikansio",
"TasksChannelsCategory": "Internet kanavat", "TasksChannelsCategory": "Internet-kanavat",
"TasksApplicationCategory": "Sovellus", "TasksApplicationCategory": "Sovellus",
"TasksLibraryCategory": "Kirjasto", "TasksLibraryCategory": "Kirjasto",
"Forced": "Pakotettu", "Forced": "Pakotettu",
"Default": "Oletus", "Default": "Oletus",
"TaskCleanActivityLogDescription": "Poistaa määritettyä vanhemmat tapahtumat aktiviteettilokista.", "TaskCleanActivityLogDescription": "Poistaa määritettyä ikää vanhemmat tapahtumat toimintahistoriasta.",
"TaskCleanActivityLog": "Tyhjennä aktiviteettiloki", "TaskCleanActivityLog": "Tyhjennä toimintahistoria",
"Undefined": "Määrittelemätön" "Undefined": "Määrittelemätön"
} }

View file

@ -3,101 +3,101 @@
"ValueSpecialEpisodeName": "Espesyal - {0}", "ValueSpecialEpisodeName": "Espesyal - {0}",
"ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya", "ValueHasBeenAddedToLibrary": "Naidagdag na ang {0} sa iyong librerya ng medya",
"UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}", "UserStoppedPlayingItemWithValues": "Natapos ni {0} ang {1} sa {2}",
"UserStartedPlayingItemWithValues": "Si {0} ay nagplaplay ng {1} sa {2}", "UserStartedPlayingItemWithValues": "Si {0} ay nagpla-play ng {1} sa {2}",
"UserPolicyUpdatedWithName": "Ang user policy ay naiupdate para kay {0}", "UserPolicyUpdatedWithName": "Ang user policy ay nai-update para kay {0}",
"UserPasswordChangedWithName": "Napalitan na ang password ni {0}", "UserPasswordChangedWithName": "Napalitan na ang password ni {0}",
"UserOnlineFromDevice": "Si {0} ay nakakonekta galing sa {1}", "UserOnlineFromDevice": "Si {0} ay naka-konekta galing sa {1}",
"UserOfflineFromDevice": "Si {0} ay nadiskonekta galing sa {1}", "UserOfflineFromDevice": "Si {0} ay na-diskonekta galing sa {1}",
"UserLockedOutWithName": "Si {0} ay nalock out", "UserLockedOutWithName": "Si {0} ay nalock out",
"UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}", "UserDownloadingItemWithValues": "Nagdadownload si {0} ng {1}",
"UserDeletedWithName": "Natanggal na is user {0}", "UserDeletedWithName": "Natanggal na is user {0}",
"UserCreatedWithName": "Nagawa na si user {0}", "UserCreatedWithName": "Nagawa na si user {0}",
"User": "User", "User": "User",
"TvShows": "Pelikula", "TvShows": "Mga Palabas sa Telebisyon",
"System": "Sistema", "System": "Sistema",
"Sync": "Pag-sync", "Sync": "Pag-sync",
"SubtitleDownloadFailureFromForItem": "Hindi naidownload ang subtitles {0} para sa {1}", "SubtitleDownloadFailureFromForItem": "Hindi nai-download ang subtitles {0} para sa {1}",
"StartupEmbyServerIsLoading": "Nagloload ang Jellyfin Server. Sandaling maghintay.", "StartupEmbyServerIsLoading": "Naglo-load ang Jellyfin Server. Mangyaring subukan ulit sandali.",
"Songs": "Kanta", "Songs": "Mga Kanta",
"Shows": "Pelikula", "Shows": "Mga Pelikula",
"ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}", "ServerNameNeedsToBeRestarted": "Kailangan irestart ang {0}",
"ScheduledTaskStartedWithName": "Nagsimula na ang {0}", "ScheduledTaskStartedWithName": "Nagsimula na ang {0}",
"ScheduledTaskFailedWithName": "Hindi gumana and {0}", "ScheduledTaskFailedWithName": "Hindi gumana ang {0}",
"ProviderValue": "Ang provider ay {0}", "ProviderValue": "Tagapagtustos: {0}",
"PluginUpdatedWithName": "Naiupdate na ang {0}", "PluginUpdatedWithName": "Naiupdate na ang {0}",
"PluginUninstalledWithName": "Naiuninstall na ang {0}", "PluginUninstalledWithName": "Naiuninstall na ang {0}",
"PluginInstalledWithName": "Nainstall na ang {0}", "PluginInstalledWithName": "Nainstall na ang {0}",
"Plugin": "Plugin", "Plugin": "Plugin",
"Playlists": "Playlists", "Playlists": "Mga Playlist",
"Photos": "Larawan", "Photos": "Mga Larawan",
"NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula", "NotificationOptionVideoPlaybackStopped": "Huminto na ang pelikula",
"NotificationOptionVideoPlayback": "Nagsimula na ang pelikula", "NotificationOptionVideoPlayback": "Nagsimula na ang pelikula",
"NotificationOptionUserLockedOut": "Nakalock out ang user", "NotificationOptionUserLockedOut": "Naka-lock out ang user",
"NotificationOptionTaskFailed": "Hindi gumana ang scheduled task", "NotificationOptionTaskFailed": "Hindi gumana ang scheduled task",
"NotificationOptionServerRestartRequired": "Kailangan irestart ang server", "NotificationOptionServerRestartRequired": "Kailangan i-restart ang server",
"NotificationOptionPluginUpdateInstalled": "Naiupdate na ang plugin", "NotificationOptionPluginUpdateInstalled": "Nai-update na ang plugin",
"NotificationOptionPluginUninstalled": "Naiuninstall na ang plugin", "NotificationOptionPluginUninstalled": "Nai-uninstall na ang plugin",
"NotificationOptionPluginInstalled": "Nainstall na ang plugin", "NotificationOptionPluginInstalled": "Nainstall na ang plugin",
"NotificationOptionPluginError": "Hindi gumagana ang plugin", "NotificationOptionPluginError": "Hindi gumagana ang plugin",
"NotificationOptionNewLibraryContent": "May bagong content na naidagdag", "NotificationOptionNewLibraryContent": "May bagong content na naidagdag",
"NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti", "NotificationOptionInstallationFailed": "Hindi nainstall ng mabuti",
"NotificationOptionCameraImageUploaded": "Naiupload na ang picture", "NotificationOptionCameraImageUploaded": "Naiupload na ang litrato",
"NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog", "NotificationOptionAudioPlaybackStopped": "Huminto na ang patugtog",
"NotificationOptionAudioPlayback": "Nagsimula na ang patugtog", "NotificationOptionAudioPlayback": "Nagsimula na ang patugtog",
"NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon", "NotificationOptionApplicationUpdateInstalled": "Naiupdate na ang aplikasyon",
"NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon", "NotificationOptionApplicationUpdateAvailable": "May bagong update ang aplikasyon",
"NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede idownload.", "NewVersionIsAvailable": "May bagong version ng Jellyfin Server na pwede i-download.",
"NameSeasonUnknown": "Hindi alam ang season", "NameSeasonUnknown": "Hindi matukoy ang season",
"NameSeasonNumber": "Season {0}", "NameSeasonNumber": "Season {0}",
"NameInstallFailed": "Hindi nainstall ang {0}", "NameInstallFailed": "Hindi nainstall ang {0}",
"MusicVideos": "Music video", "MusicVideos": "Mga Music video",
"Music": "Kanta", "Music": "Mga Kanta",
"Movies": "Pelikula", "Movies": "Mga Pelikula",
"MixedContent": "Halo-halong content", "MixedContent": "Halo-halong content",
"MessageServerConfigurationUpdated": "Naiupdate na ang server configuration", "MessageServerConfigurationUpdated": "Naiupdate na ang server configuration",
"MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}", "MessageNamedServerConfigurationUpdatedWithValue": "Naiupdate na ang server configuration section {0}",
"MessageApplicationUpdatedTo": "Ang Jellyfin Server ay naiupdate to {0}", "MessageApplicationUpdatedTo": "Ang bersyon ng Jellyfin Server ay naiupdate sa {0}",
"MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server", "MessageApplicationUpdated": "Naiupdate na ang Jellyfin Server",
"Latest": "Pinakabago", "Latest": "Pinakabago",
"LabelRunningTimeValue": "Oras: {0}", "LabelRunningTimeValue": "Oras: {0}",
"LabelIpAddressValue": "Ang IP Address ay {0}", "LabelIpAddressValue": "IP address: {0}",
"ItemRemovedWithName": "Naitanggal ang {0} sa librerya", "ItemRemovedWithName": "Naitanggal ang {0} sa librerya",
"ItemAddedWithName": "Naidagdag ang {0} sa librerya", "ItemAddedWithName": "Naidagdag ang {0} sa librerya",
"Inherit": "Manahin", "Inherit": "Manahin",
"HeaderRecordingGroups": "Pagtatalang Grupo", "HeaderRecordingGroups": "Pagtatalang Grupo",
"HeaderNextUp": "Susunod", "HeaderNextUp": "Susunod",
"HeaderLiveTV": "Live TV", "HeaderLiveTV": "Live TV",
"HeaderFavoriteSongs": "Paboritong Kanta", "HeaderFavoriteSongs": "Mga Paboritong Kanta",
"HeaderFavoriteShows": "Paboritong Pelikula", "HeaderFavoriteShows": "Mga Paboritong Pelikula",
"HeaderFavoriteEpisodes": "Paboritong Episodes", "HeaderFavoriteEpisodes": "Mga Paboritong Episode",
"HeaderFavoriteArtists": "Paboritong Artista", "HeaderFavoriteArtists": "Mga Paboritong Artista",
"HeaderFavoriteAlbums": "Paboritong Albums", "HeaderFavoriteAlbums": "Mga Paboritong Album",
"HeaderContinueWatching": "Ituloy Manood", "HeaderContinueWatching": "Magpatuloy sa Panonood",
"HeaderAlbumArtists": "Artista ng Album", "HeaderAlbumArtists": "Mga Artista ng Album",
"Genres": "Kategorya", "Genres": "Mga Kategorya",
"Folders": "Folders", "Folders": "Mga Folder",
"Favorites": "Paborito", "Favorites": "Mga Paborito",
"FailedLoginAttemptWithUserName": "maling login galing {0}", "FailedLoginAttemptWithUserName": "Maling login galing kay/sa {0}",
"DeviceOnlineWithName": "nakakonekta si {0}", "DeviceOnlineWithName": "Nakakonekta si/ang {0}",
"DeviceOfflineWithName": "nadiskonekta si {0}", "DeviceOfflineWithName": "Nadiskonekta si/ang {0}",
"Collections": "Koleksyon", "Collections": "Mga Koleksyon",
"ChapterNameValue": "Kabanata {0}", "ChapterNameValue": "Kabanata {0}",
"Channels": "Channel", "Channels": "Mga Channel",
"CameraImageUploadedFrom": "May bagong larawan na naupload galing {0}", "CameraImageUploadedFrom": "May bagong larawan na naupload galing sa/kay {0}",
"Books": "Libro", "Books": "Mga Libro",
"AuthenticationSucceededWithUserName": "{0} na patunayan", "AuthenticationSucceededWithUserName": "Napatunayan si/ang {0}",
"Artists": "Artista", "Artists": "Mga Artista",
"Application": "Aplikasyon", "Application": "Aplikasyon",
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}", "AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
"Albums": "Albums", "Albums": "Mga Album",
"TaskRefreshLibrary": "Suriin and Librerya ng Medya", "TaskRefreshLibrary": "Suriin and Librerya ng Medya",
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.", "TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata.",
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata", "TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
"TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.", "TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng sistema.",
"TasksChannelsCategory": "Palabas sa internet", "TasksChannelsCategory": "Palabas sa internet",
"TasksLibraryCategory": "Librerya", "TasksLibraryCategory": "Librerya",
"TasksMaintenanceCategory": "Pagpapanatili", "TasksMaintenanceCategory": "Pagpapanatili",
"HomeVideos": "Sariling pelikula", "HomeVideos": "Sariling video/pelikula",
"TaskRefreshPeopleDescription": "Ini-update ang metadata para sa mga aktor at direktor sa iyong librerya ng medya.", "TaskRefreshPeopleDescription": "Ini-update ang metadata para sa mga aktor at direktor sa iyong librerya ng medya.",
"TaskRefreshPeople": "I-refresh ang Tauhan", "TaskRefreshPeople": "I-refresh ang Tauhan",
"TaskDownloadMissingSubtitlesDescription": "Hinahanap sa internet ang mga nawawalang subtiles base sa metadata configuration.", "TaskDownloadMissingSubtitlesDescription": "Hinahanap sa internet ang mga nawawalang subtiles base sa metadata configuration.",
@ -105,14 +105,17 @@
"TaskRefreshChannelsDescription": "Ni-rerefresh ang impormasyon sa internet channels.", "TaskRefreshChannelsDescription": "Ni-rerefresh ang impormasyon sa internet channels.",
"TaskRefreshChannels": "I-refresh ang Channels", "TaskRefreshChannels": "I-refresh ang Channels",
"TaskCleanTranscodeDescription": "Binubura ang transcode files na mas matanda ng isang araw.", "TaskCleanTranscodeDescription": "Binubura ang transcode files na mas matanda ng isang araw.",
"TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa automatikong pag update.", "TaskUpdatePluginsDescription": "Nag download at install ng updates sa plugins na naka configure para sa awtomatikong pag-update.",
"TaskUpdatePlugins": "I-update ang Plugins", "TaskUpdatePlugins": "I-update ang Plugins",
"TaskCleanLogsDescription": "Binubura and files ng talaan na mas mantanda ng {0} araw.", "TaskCleanLogsDescription": "Binubura and files ng talaan na mas mantanda ng {0} araw.",
"TaskCleanTranscode": "Linisin and Direktoryo ng Transcode", "TaskCleanTranscode": "Linisin and Direktoryo ng Transcode",
"TaskCleanLogs": "Linisin and Direktoryo ng Talaan", "TaskCleanLogs": "Linisin and Direktoryo ng Talaan",
"TaskRefreshLibraryDescription": "Sinusuri ang iyong librerya ng medya para sa bagong files at irefresh ang metadata.", "TaskRefreshLibraryDescription": "Sinusuri ang iyong librerya ng medya para sa bagong files at irefresh ang metadata.",
"TaskCleanCache": "Linisin and Direktoryo ng Cache", "TaskCleanCache": "Linisin and Direktoryo ng Cache",
"TasksApplicationCategory": "Application", "TasksApplicationCategory": "Aplikasyon",
"TaskCleanActivityLog": "Linisin ang Tala ng Aktibidad", "TaskCleanActivityLog": "Linisin ang Tala ng Aktibidad",
"TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas matanda sa naka configure na edad." "TaskCleanActivityLogDescription": "Tanggalin ang mga tala ng aktibidad na mas luma sa nakatakda na edad.",
"Default": "Default",
"Undefined": "Hindi tiyak",
"Forced": "Sapilitan"
} }

View file

@ -1,5 +1,5 @@
{ {
"Albums": "संग्रह", "Albums": "एल्बम",
"HeaderRecordingGroups": "रिकॉर्डिंग समूह", "HeaderRecordingGroups": "रिकॉर्डिंग समूह",
"HeaderNextUp": "इसके बाद", "HeaderNextUp": "इसके बाद",
"HeaderLiveTV": "लाइव टीवी", "HeaderLiveTV": "लाइव टीवी",
@ -26,5 +26,30 @@
"AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत", "AuthenticationSucceededWithUserName": "सफलता से प्रमाणीकृत",
"Artists": "कलाकारों", "Artists": "कलाकारों",
"Application": "एप्लिकेशन", "Application": "एप्लिकेशन",
"AppDeviceValues": "एप: {0}, मशीन: {1}" "AppDeviceValues": "एप: {0}, उपकरण: {1}",
"NotificationOptionPluginUninstalled": "प्लगइन अनइंस्टाल हो गया",
"NotificationOptionPluginInstalled": "प्लगइन इनस्टॉल हो गया",
"NotificationOptionPluginError": "प्लगइन फ़ैल हो गया",
"NotificationOptionInstallationFailed": "इंस्टालेशन फ़ैल हो गया",
"NotificationOptionAudioPlaybackStopped": "संगीत बंद कर दिया गया",
"NotificationOptionAudioPlayback": "संगीत शुरू कर दिया गया",
"NotificationOptionCameraImageUploaded": "कैमरा फोटो अपलोड किया गया",
"NotificationOptionApplicationUpdateInstalled": "एप्लीकेशन अपडेट इनस्टॉल कर दिया है",
"NotificationOptionApplicationUpdateAvailable": "एप्लीकेशन अपडेट उपलभ्द है",
"NewVersionIsAvailable": "जेलीफिन सर्वर का एक नया वर्जन डाउनलोड के लिए उपलब्ध है।",
"NameSeasonUnknown": "अनजान भाग",
"NameSeasonNumber": "भाग {0}",
"NameInstallFailed": "{0} इनस्टॉल करते समय फेल हो गया है",
"MusicVideos": "संगीत वीडियो",
"Music": "संगीत",
"Movies": "फ़िल्म",
"MixedContent": "मिला-जुला कंटेंट",
"MessageServerConfigurationUpdated": "सर्वर कॉन्फ़िगरेशन अपडेट हो गया है",
"MessageNamedServerConfigurationUpdatedWithValue": "सर्वर कॉन्फ़िगरेशन भाग {0} अपडेट हो गया है",
"MessageApplicationUpdatedTo": "जैलीफिन सर्वर {0} में अपडेट हो गया है",
"MessageApplicationUpdated": "जैलीफिन सर्वर अपडेट हो गया है",
"Latest": "सबसे नया",
"LabelIpAddressValue": "आई पी एड्रेस: {0}",
"ItemRemovedWithName": "{0} लाइब्रेरी में से निकाल दिया है",
"HomeVideos": "होम वीडियोस"
} }

View file

@ -1,122 +1,122 @@
{ {
"Albums": "Álbomdar", "Albums": "Älbomdar",
"AppDeviceValues": "Qoldanba: {0}, Qurylǵy: {1}", "AppDeviceValues": "Qoldanba: {0}, Qūrylğy: {1}",
"Application": "Qoldanba", "Application": "Qoldanba",
"Artists": "Oryndaýshylar", "Artists": "Oryndaylar",
"AuthenticationSucceededWithUserName": "{0} túpnusqalyq rastalýy sátti aıaqtaldy", "AuthenticationSucceededWithUserName": "{0} tüpnūsqalyq rastaluy sättı aiaqtaldy",
"Books": "Kitaptar", "Books": "Kıtaptar",
"CameraImageUploadedFrom": "{0} kamerasynan jańa sýret júktep salyndy", "CameraImageUploadedFrom": "{0} kamerasynan jaŋa suret jüktep salyndy",
"Channels": "Arnalar", "Channels": "Arnalar",
"ChapterNameValue": "{0}-sahna", "ChapterNameValue": "{0}-sahna",
"Collections": "Jıyntyqtar", "Collections": "Jiyntyqtar",
"DeviceOfflineWithName": "{0} ajyratylǵan", "DeviceOfflineWithName": "{0} ajyratylğan",
"DeviceOnlineWithName": "{0} qosylǵan", "DeviceOnlineWithName": "{0} qosylğan",
"FailedLoginAttemptWithUserName": "{0} tarapynan kirý áreketi sátsiz aıaqtaldy", "FailedLoginAttemptWithUserName": "{0} tarapynan kıru äreketı sätsız aiaqtaldy",
"Favorites": "Tańdaýlylar", "Favorites": "Taŋdaulylar",
"Folders": "Qaltalar", "Folders": "Qaltalar",
"Genres": "Janrlar", "Genres": "Janrlar",
"HeaderAlbumArtists": "Álbom oryndaýshylary", "HeaderAlbumArtists": "Älbom oryndauşylary",
"HeaderContinueWatching": "Qaraýdy jalǵastyrý", "HeaderContinueWatching": "Qaraudy jalğastyru",
"HeaderFavoriteAlbums": "Tańdaýly álbomdar", "HeaderFavoriteAlbums": "Taŋdauly älbomdar",
"HeaderFavoriteArtists": "Tańdaýly oryndaýshylar", "HeaderFavoriteArtists": "Taŋdauly oryndauşylar",
"HeaderFavoriteEpisodes": "Tańdaýly bólimder", "HeaderFavoriteEpisodes": "Taŋdauly telebölımder",
"HeaderFavoriteShows": "Tańdaýly kórsetimder", "HeaderFavoriteShows": "Taŋdauly körsetımder",
"HeaderFavoriteSongs": "Tańdaýly áýender", "HeaderFavoriteSongs": "Taŋdauly äuender",
"HeaderLiveTV": "Efır", "HeaderLiveTV": "Efir",
"HeaderNextUp": "Kezekti", "HeaderNextUp": "Kezektı",
"HeaderRecordingGroups": "Jazba toptary", "HeaderRecordingGroups": "Jazba toptary",
"HomeVideos": "Úılik beıneler", "HomeVideos": "Üilık beineler",
"Inherit": "Muraǵa ıelený", "Inherit": "İelenu",
"ItemAddedWithName": "{0} tasyǵyshhanaǵa ústeldi", "ItemAddedWithName": "{0} tasyğyşhanağa üstelindı",
"ItemRemovedWithName": "{0} tasyǵyshhanadan alastaldy", "ItemRemovedWithName": "{0} tasyğyşhanadan alastaldy",
"LabelIpAddressValue": "IP-mekenjaıy: {0}", "LabelIpAddressValue": "IP-mekenjaiy: {0}",
"LabelRunningTimeValue": "Oınatý ýaqyty: {0}", "LabelRunningTimeValue": "Oinatu uaqyty: {0}",
"Latest": "Eń keıingi", "Latest": "Eŋ keiıngı",
"MessageApplicationUpdated": "Jellyfin Serveri jańartyldy", "MessageApplicationUpdated": "Jellyfin Serverı jaŋartyldy",
"MessageApplicationUpdatedTo": "Jellyfin Serveri {0} nusqasyna jańartyldy", "MessageApplicationUpdatedTo": "Jellyfin Serverı {0} nūsqasyna jaŋartyldy",
"MessageNamedServerConfigurationUpdatedWithValue": "Server konfıgýrasýasynyń {0} bólimi jańartyldy", "MessageNamedServerConfigurationUpdatedWithValue": "Server teŋşelımderınıŋ {0} bölımı jaŋartyldy",
"MessageServerConfigurationUpdated": "Server konfıgýrasıasy jańartyldy", "MessageServerConfigurationUpdated": "Server teŋşelımderı jaŋartyldy",
"MixedContent": "Aralas mazmun", "MixedContent": "Aralas mazmūn",
"Movies": "Fılmder", "Movies": "Filmder",
"Music": "Mýzyka", "Music": "Muzyka",
"MusicVideos": "Mýzykalyq beıneler", "MusicVideos": "Muzykalyq beineler",
"NameInstallFailed": "{0} ornatylýy sátsiz", "NameInstallFailed": "{0} ornatyluy sätsız",
"NameSeasonNumber": "{0}-maýsym", "NameSeasonNumber": "{0}-mausym",
"NameSeasonUnknown": "Belgisiz maýsym", "NameSeasonUnknown": "Belgısız mausym",
"NewVersionIsAvailable": "Jańa Jellyfin Server nusqasy júktep alýǵa qoljetimdi.", "NewVersionIsAvailable": "Jaŋa Jellyfin Server nūsqasy jüktep aluğa qoljetımdı.",
"NotificationOptionApplicationUpdateAvailable": "Qoldanba jańartýy qoljetimdi", "NotificationOptionApplicationUpdateAvailable": "Qoldanba jaŋartuy qoljetımdı",
"NotificationOptionApplicationUpdateInstalled": "Qoldanba jańartýy ornatyldy", "NotificationOptionApplicationUpdateInstalled": "Qoldanba jaŋartuy ornatyldy",
"NotificationOptionAudioPlayback": "Dybys oınatýy bastaldy", "NotificationOptionAudioPlayback": "Dybys oinatuy bastaldy",
"NotificationOptionAudioPlaybackStopped": "Dybys oınatýy toqtatyldy", "NotificationOptionAudioPlaybackStopped": "Dybys oinatuy toqtatyldy",
"NotificationOptionCameraImageUploaded": "Kameradan fotosýret júktep salynǵan", "NotificationOptionCameraImageUploaded": "Kameradan fotosuret jüktep salynğan",
"NotificationOptionInstallationFailed": "Ornatý sátsizdigi", "NotificationOptionInstallationFailed": "Ornatu sätsızdıgı",
"NotificationOptionNewLibraryContent": "Jańa mazmun ústelgen", "NotificationOptionNewLibraryContent": "Jaŋa mazmūn üstelıngen",
"NotificationOptionPluginError": "Plagın sátsizdigi", "NotificationOptionPluginError": "Plagin sätsızdıgı",
"NotificationOptionPluginInstalled": "Plagın ornatyldy", "NotificationOptionPluginInstalled": "Plagin ornatyldy",
"NotificationOptionPluginUninstalled": "Plagın ornatýy boldyrylmady", "NotificationOptionPluginUninstalled": "Plagin ornatuy boldyrylmady",
"NotificationOptionPluginUpdateInstalled": "Plagın jańartýy ornatyldy", "NotificationOptionPluginUpdateInstalled": "Plagin jaŋartuy ornatyldy",
"NotificationOptionServerRestartRequired": "Serverdi qaıta iske qosý qajet", "NotificationOptionServerRestartRequired": "Serverdı qaita ıske qosu qajet",
"NotificationOptionTaskFailed": "Josparlaǵan tapsyrma sátsizdigi", "NotificationOptionTaskFailed": "Josparlağan tapsyrma sätsızdıgı",
"NotificationOptionUserLockedOut": "Paıdalanýshy qursaýly", "NotificationOptionUserLockedOut": "Paidalanuşy qūrsauly",
"NotificationOptionVideoPlayback": "Beıne oınatýy bastaldy", "NotificationOptionVideoPlayback": "Beine oinatuy bastaldy",
"NotificationOptionVideoPlaybackStopped": "Beıne oınatýy toqtatyldy", "NotificationOptionVideoPlaybackStopped": "Beine oinatuy toqtatyldy",
"Photos": "Fotosýretter", "Photos": "Fotosuretter",
"Playlists": "Oınatý tizimderi", "Playlists": "Oinatu tızımderı",
"Plugin": "Plagın", "Plugin": "Plagin",
"PluginInstalledWithName": "{0} ornatyldy", "PluginInstalledWithName": "{0} ornatyldy",
"PluginUninstalledWithName": "{0} joıyldy", "PluginUninstalledWithName": "{0} joiyldy",
"PluginUpdatedWithName": "{0} jańartyldy", "PluginUpdatedWithName": "{0} jaŋartyldy",
"ProviderValue": "Jetkizýshi: {0}", "ProviderValue": "Jetkızuşı: {0}",
"ScheduledTaskFailedWithName": "{0} sátsiz", "ScheduledTaskFailedWithName": "{0} sätsız",
"ScheduledTaskStartedWithName": "{0} iske qosyldy", "ScheduledTaskStartedWithName": "{0} ıske qosyldy",
"ServerNameNeedsToBeRestarted": "{0} qaıta iske qosý qajet", "ServerNameNeedsToBeRestarted": "{0} qaita ıske qosu qajet",
"Shows": "Kórsetimder", "Shows": "Körsetımder",
"Songs": "Áýender", "Songs": "Äuender",
"StartupEmbyServerIsLoading": "Jellyfin Server júktelýde. Áreketti kóp uzamaı qaıtalańyz.", "StartupEmbyServerIsLoading": "Jellyfin Server jüktelude. Ärekettı köp ūzamai qaitalaŋyz.",
"SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз", "SubtitleDownloadFailureForItem": "Субтитрлер {0} үшін жүктеліп алынуы сәтсіз",
"SubtitleDownloadFailureFromForItem": "{1} úshin sýbtıtrlerdi {0} kózinen júktep alý sátsiz", "SubtitleDownloadFailureFromForItem": "{1} üşın subtitrlerdı {0} közınen jüktep alu sätsız",
"Sync": "Úndestirý", "Sync": "Ündestıru",
"System": "Júıe", "System": "Jüie",
"TvShows": "TD-kórsetimder", "TvShows": "TD-körsetımder",
"User": "Paıdalanýshy", "User": "Paidalanuşy",
"UserCreatedWithName": "Paıdalanýshy {0} jasalǵan", "UserCreatedWithName": "Paidalanuşy {0} jasalğan",
"UserDeletedWithName": "Paıdalanýshy {0} joıylǵan", "UserDeletedWithName": "Paidalanuşy {0} joiylğan",
"UserDownloadingItemWithValues": "{0} mynany júktep alýda: {1}", "UserDownloadingItemWithValues": "{0} — {1} jüktep aluda",
"UserLockedOutWithName": "Paıdalanýshy {0} qursaýly", "UserLockedOutWithName": "Paidalanuşy {0} qūrsaulanğan",
"UserOfflineFromDevice": "{0} - {1} tarapynan ajyratylǵan", "UserOfflineFromDevice": "{0} — {1} tarapynan ajyratyldy",
"UserOnlineFromDevice": "{0} - {1} arqyly qosylǵan", "UserOnlineFromDevice": "{0} — {1} tarapynan qosyldy",
"UserPasswordChangedWithName": "Paıdalanýshy {0} úshin paról ózgertildi", "UserPasswordChangedWithName": "Paidalanuşy {0} üşın paröl özgertıldı",
"UserPolicyUpdatedWithName": "Paıdalanýshy {0} úshin saıasattary jańartyldy", "UserPolicyUpdatedWithName": "Paidalanuşy {0} üşın saiasattary jaŋartyldy",
"UserStartedPlayingItemWithValues": "{0} - {1} oınatýyn {2} bastady", "UserStartedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuda",
"UserStoppedPlayingItemWithValues": "{0} - {1} oınatýyn {2} toqtatty", "UserStoppedPlayingItemWithValues": "{0} — {2} tarapynan {1} oinatuyn toqtatty",
"ValueHasBeenAddedToLibrary": "{0} (tasyǵyshhanaǵa ústelindi)", "ValueHasBeenAddedToLibrary": "{0} tasyğyşhanağa üstelındı",
"ValueSpecialEpisodeName": "Arnaıy - {0}", "ValueSpecialEpisodeName": "Arnaiy - {0}",
"VersionNumber": "Nusqasy {0}", "VersionNumber": "Nūsqasy {0}",
"Default": "Ádepki", "Default": "Ädepkı",
"TaskDownloadMissingSubtitles": "Joq sýbtıtrlerdi júktep alý", "TaskDownloadMissingSubtitles": "Joq subtitrlerdı jüktep alu",
"TaskRefreshChannels": "Arnalardy jańartý", "TaskRefreshChannels": "Arnalardy jaŋğyrtu",
"TaskCleanTranscode": "Qaıta kodtaý katalogyn tazalaý", "TaskCleanTranscode": "Qaita kodtau katalogyn tazalau",
"TaskUpdatePlugins": "Plagınderdi jańartý", "TaskUpdatePlugins": "Plaginderdı jaŋartu",
"TaskRefreshPeople": "Adamdardy jańartý", "TaskRefreshPeople": "Adamdardy jaŋğyrtu",
"TaskCleanLogs": "Jurnal katalogyn tazalaý", "TaskCleanLogs": "Jūrnal katalogyn tazalau",
"TaskRefreshLibrary": "Tasyǵyshhanany skanerleý", "TaskRefreshLibrary": "Tasyğyşhanany skanerleu",
"TaskRefreshChapterImages": "Sahna keskinderin shyǵaryp alý", "TaskRefreshChapterImages": "Sahna suretterın şyğaryp alu",
"TaskCleanCache": "Kesh katalogyn tazalaý", "TaskCleanCache": "Keş katalogyn tazalau",
"TaskCleanActivityLog": "Áreket jurnalyn tazalaý", "TaskCleanActivityLog": "Äreket jūrnalyn tazalau",
"TasksChannelsCategory": "Internet-arnalar", "TasksChannelsCategory": "Internet-arnalar",
"TasksApplicationCategory": "Qoldanba", "TasksApplicationCategory": "Qoldanba",
"TasksLibraryCategory": "Tasyǵyshhana", "TasksLibraryCategory": "Tasyğyşhana",
"TasksMaintenanceCategory": "Qyzmet kórsetý", "TasksMaintenanceCategory": "Qyzmet körsetu",
"Undefined": "Anyqtalmady", "Undefined": "Anyqtalmağan",
"Forced": "Májbúrli", "Forced": "Mäjbürlı",
"TaskDownloadMissingSubtitlesDescription": "Metaderekter teńshelimi negіzіnde joq sýbtıtrlerdі Internetten іzdeıdі.", "TaskDownloadMissingSubtitlesDescription": "Metaderekter teŋşelımderı negızınde joq subtitrlerdı Internetten ızdeidı.",
"TaskRefreshChannelsDescription": "Internet-arnalar málimetterin jańartady.", "TaskRefreshChannelsDescription": "Internet-arnalar mälımetterın jaŋğyrtady.",
"TaskCleanTranscodeDescription": "Bіr kúnnen asqan qaıta kodtaý faıldaryn joıady.", "TaskCleanTranscodeDescription": "Bіr künnen asqan qaita kodtau faildaryn joiady.",
"TaskUpdatePluginsDescription": "Avtomatty túrde jańartýǵa teńshelgen plagınder úshin jańartýlardy júktep alady jáne ornatady.", "TaskUpdatePluginsDescription": "Avtomatty türde jaŋartuğa teŋşelgen plaginder üşın jaŋartulardy jüktep alady jäne ornatady.",
"TaskRefreshPeopleDescription": "Tasyǵyshhanadaǵy aktórler men rejısórler metaderekterіn jańartady.", "TaskRefreshPeopleDescription": "Tasyğyşhanadağy aktörler men rejisörler metaderekterın jaŋartady.",
"TaskCleanLogsDescription": "{0} kúnnen asqan jurnal faıldaryn joıady.", "TaskCleanLogsDescription": "{0} künnen asqan jūrnal faildaryn joiady.",
"TaskRefreshLibraryDescription": "Tasyǵyshhanadaǵy jańa faıldardy skanerleıdі jáne metaderekterdі jańartady.", "TaskRefreshLibraryDescription": "Tasyğyşhanadağy jaŋa faildardy skanerleidі jäne metaderekterdı jaŋğyrtady.",
"TaskRefreshChapterImagesDescription": "Sahnalarǵa bólіngen beıneler úshіn nobaılar jasaıdy.", "TaskRefreshChapterImagesDescription": "Sahnalary bar beineler üşіn nobailar jasaidy.",
"TaskCleanCacheDescription": "Júıede qajet emes keshtelgen faıldardy joıady.", "TaskCleanCacheDescription": "Jüiede qajet emes keştelgen faildardy joiady.",
"TaskCleanActivityLogDescription": "Áreketter jurnalyndaǵy teńshelgen jasynan asqan jazbalaly joıady." "TaskCleanActivityLogDescription": "Äreket jūrnalyndağy teŋşelgen jasynan asqan jazbalary joiady."
} }

View file

@ -117,5 +117,6 @@
"TaskCleanActivityLog": "Tøm aktivitetslogg", "TaskCleanActivityLog": "Tøm aktivitetslogg",
"Undefined": "Udefinert", "Undefined": "Udefinert",
"Forced": "Tvungen", "Forced": "Tvungen",
"Default": "Standard" "Default": "Standard",
"TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen."
} }

View file

@ -89,8 +89,8 @@
"UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены", "UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
"UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}", "UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
"UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}", "UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)", "ValueHasBeenAddedToLibrary": "{0} добавлено в медиатеку",
"ValueSpecialEpisodeName": "Специальный эпизод - {0}", "ValueSpecialEpisodeName": "Спецэпизод - {0}",
"VersionNumber": "Версия {0}", "VersionNumber": "Версия {0}",
"TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров", "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
"TaskRefreshChannels": "Обновление каналов", "TaskRefreshChannels": "Обновление каналов",

View file

@ -117,5 +117,5 @@
"TaskCleanActivityLogDescription": "Radera aktivitets logg inlägg som är äldre än definerad ålder.", "TaskCleanActivityLogDescription": "Radera aktivitets logg inlägg som är äldre än definerad ålder.",
"TaskCleanActivityLog": "Rensa Aktivitets Logg", "TaskCleanActivityLog": "Rensa Aktivitets Logg",
"Undefined": "odefinierad", "Undefined": "odefinierad",
"Forced": "Tvinga" "Forced": "Tvingad"
} }

View file

@ -12,7 +12,7 @@
"DeviceOfflineWithName": "{0} bağlantısı kesildi", "DeviceOfflineWithName": "{0} bağlantısı kesildi",
"DeviceOnlineWithName": "{0} bağlı", "DeviceOnlineWithName": "{0} bağlı",
"FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu", "FailedLoginAttemptWithUserName": "{0} adresinden giriş başarısız oldu",
"Favorites": "Favorilerim", "Favorites": "Favoriler",
"Folders": "Klasörler", "Folders": "Klasörler",
"Genres": "Türler", "Genres": "Türler",
"HeaderAlbumArtists": "Albüm Sanatçıları", "HeaderAlbumArtists": "Albüm Sanatçıları",
@ -117,5 +117,6 @@
"TaskCleanActivityLog": "İşlem Günlüğünü Temizle", "TaskCleanActivityLog": "İşlem Günlüğünü Temizle",
"TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.", "TaskCleanActivityLogDescription": "Belirtilen sureden daha eski etkinlik log kayıtları silindi.",
"Undefined": "Bilinmeyen", "Undefined": "Bilinmeyen",
"Default": "Varsayılan" "Default": "Varsayılan",
"Forced": "Zorla"
} }

View file

@ -3,7 +3,7 @@
"Favorites": "Yêu Thích", "Favorites": "Yêu Thích",
"Folders": "Thư Mục", "Folders": "Thư Mục",
"Genres": "Thể Loại", "Genres": "Thể Loại",
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ", "HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp", "HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp", "HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim", "Movies": "Phim",
@ -13,7 +13,7 @@
"Songs": "Các Bài Hát", "Songs": "Các Bài Hát",
"Sync": "Đồng Bộ", "Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}", "ValueSpecialEpisodeName": "Đặc Biệt - {0}",
"Albums": "Albums", "Albums": "Tuyển Tập",
"Artists": "Các Nghệ Sĩ", "Artists": "Các Nghệ Sĩ",
"TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.", "TaskDownloadMissingSubtitlesDescription": "Tìm kiếm phụ đề bị thiếu trên Internet dựa trên cấu hình dữ liệu mô tả.",
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu", "TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",

View file

@ -113,5 +113,9 @@
"TaskCleanCache": "清理緩存目錄", "TaskCleanCache": "清理緩存目錄",
"TasksChannelsCategory": "互聯網頻道", "TasksChannelsCategory": "互聯網頻道",
"TasksLibraryCategory": "庫", "TasksLibraryCategory": "庫",
"TaskRefreshPeople": "刷新人物" "TaskRefreshPeople": "刷新人物",
"TaskCleanActivityLog": "清理活動記錄",
"Undefined": "未定義",
"Forced": "強制",
"Default": "預設"
} }

View file

@ -1,6 +1,6 @@
{ {
"Albums": "專輯", "Albums": "專輯",
"AppDeviceValues": "軟體{0},裝置:{1}", "AppDeviceValues": "App{0},裝置:{1}",
"Application": "應用程式", "Application": "應用程式",
"Artists": "演出者", "Artists": "演出者",
"AuthenticationSucceededWithUserName": "{0} 成功授權", "AuthenticationSucceededWithUserName": "{0} 成功授權",

View file

@ -77,6 +77,8 @@ chb|||Chibcha|chibcha
che||ce|Chechen|tchétchène che||ce|Chechen|tchétchène
chg|||Chagatai|djaghataï chg|||Chagatai|djaghataï
chi|zho|zh|Chinese|chinois chi|zho|zh|Chinese|chinois
chi|zho|zh-tw|Chinese; Traditional|chinois
chi|zho|zh-hk|Chinese; Hong Kong|chinois
chk|||Chuukese|chuuk chk|||Chuukese|chuuk
chm|||Mari|mari chm|||Mari|mari
chn|||Chinook jargon|chinook, jargon chn|||Chinook jargon|chinook, jargon

View file

@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.MediaEncoder
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(",", video.Path)); _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(',', video.Path));
success = false; success = false;
break; break;
} }

View file

@ -112,8 +112,6 @@ namespace Emby.Server.Implementations.Plugins
{ {
assembly = Assembly.LoadFrom(file); assembly = Assembly.LoadFrom(file);
// This force loads all reference dll's that the plugin uses in the try..catch block.
// Removing this will cause JF to bomb out if referenced dll's cause issues.
assembly.GetExportedTypes(); assembly.GetExportedTypes();
} }
catch (FileLoadException ex) catch (FileLoadException ex)
@ -122,6 +120,20 @@ namespace Emby.Server.Implementations.Plugins
ChangePluginState(plugin, PluginStatus.Malfunctioned); ChangePluginState(plugin, PluginStatus.Malfunctioned);
continue; continue;
} }
catch (TypeLoadException ex) // Undocumented exception
{
_logger.LogError(ex, "Failed to load assembly {Path}. This error occurs when a plugin references an incompatible version of one of the shared libraries. Disabling plugin.", file);
ChangePluginState(plugin, PluginStatus.NotSupported);
continue;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{
_logger.LogError(ex, "Failed to load assembly {Path}. Unknown exception was thrown. Disabling plugin.", file);
ChangePluginState(plugin, PluginStatus.Malfunctioned);
continue;
}
_logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file); _logger.LogInformation("Loaded assembly {Assembly} from {Path}", assembly.FullName, file);
yield return assembly; yield return assembly;
@ -336,7 +348,7 @@ namespace Emby.Server.Implementations.Plugins
try try
{ {
var data = JsonSerializer.Serialize(manifest, _jsonOptions); var data = JsonSerializer.Serialize(manifest, _jsonOptions);
File.WriteAllText(Path.Combine(path, "meta.json"), data, Encoding.UTF8); File.WriteAllText(Path.Combine(path, "meta.json"), data);
return true; return true;
} }
#pragma warning disable CA1031 // Do not catch general exception types #pragma warning disable CA1031 // Do not catch general exception types
@ -374,7 +386,7 @@ namespace Emby.Server.Implementations.Plugins
private LocalPlugin? GetPluginByAssembly(Assembly assembly) private LocalPlugin? GetPluginByAssembly(Assembly assembly)
{ {
// Find which plugin it is by the path. // Find which plugin it is by the path.
return _plugins.FirstOrDefault(p => string.Equals(p.Path, Path.GetDirectoryName(assembly.Location), StringComparison.Ordinal)); return _plugins.FirstOrDefault(p => p.DllFiles.Contains(assembly.Location, StringComparer.Ordinal));
} }
/// <summary> /// <summary>
@ -421,15 +433,17 @@ namespace Emby.Server.Implementations.Plugins
{ {
plugin.Instance = instance; plugin.Instance = instance;
var manifest = plugin.Manifest; var manifest = plugin.Manifest;
var pluginStr = plugin.Instance.Version.ToString(); var pluginStr = instance.Version.ToString();
bool changed = false; bool changed = false;
if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal)) if (string.Equals(manifest.Version, pluginStr, StringComparison.Ordinal)
|| manifest.Id != instance.Id)
{ {
// If a plugin without a manifest failed to load due to an external issue (eg config), // If a plugin without a manifest failed to load due to an external issue (eg config),
// this updates the manifest to the actual plugin values. // this updates the manifest to the actual plugin values.
manifest.Version = pluginStr; manifest.Version = pluginStr;
manifest.Name = plugin.Instance.Name; manifest.Name = plugin.Instance.Name;
manifest.Description = plugin.Instance.Description; manifest.Description = plugin.Instance.Description;
manifest.Id = plugin.Instance.Id;
changed = true; changed = true;
} }
@ -505,39 +519,43 @@ namespace Emby.Server.Implementations.Plugins
return _plugins.Remove(plugin); return _plugins.Remove(plugin);
} }
private LocalPlugin LoadManifest(string dir) internal LocalPlugin LoadManifest(string dir)
{ {
Version? version; Version? version;
PluginManifest? manifest = null; PluginManifest? manifest = null;
var metafile = Path.Combine(dir, "meta.json"); var metafile = Path.Combine(dir, "meta.json");
if (File.Exists(metafile)) if (File.Exists(metafile))
{ {
// Only path where this stays null is when File.ReadAllBytes throws an IOException
byte[] data = null!;
try try
{ {
var data = File.ReadAllText(metafile, Encoding.UTF8); data = File.ReadAllBytes(metafile);
manifest = JsonSerializer.Deserialize<PluginManifest>(data, _jsonOptions); manifest = JsonSerializer.Deserialize<PluginManifest>(data, _jsonOptions);
} }
#pragma warning disable CA1031 // Do not catch general exception types catch (IOException ex)
catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
{ {
_logger.LogError(ex, "Error deserializing {Path}.", dir); _logger.LogError(ex, "Error reading file {Path}.", dir);
} }
} catch (JsonException ex)
if (manifest != null)
{
if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
{ {
targetAbi = _minimumVersion; _logger.LogError(ex, "Error deserializing {Json}.", Encoding.UTF8.GetString(data!));
} }
if (!Version.TryParse(manifest.Version, out version)) if (manifest != null)
{ {
manifest.Version = _minimumVersion.ToString(); if (!Version.TryParse(manifest.TargetAbi, out var targetAbi))
} {
targetAbi = _minimumVersion;
}
return new LocalPlugin(dir, _appVersion >= targetAbi, manifest); if (!Version.TryParse(manifest.Version, out version))
{
manifest.Version = _minimumVersion.ToString();
}
return new LocalPlugin(dir, _appVersion >= targetAbi, manifest);
}
} }
// No metafile, so lets see if the folder is versioned. // No metafile, so lets see if the folder is versioned.
@ -559,7 +577,7 @@ namespace Emby.Server.Implementations.Plugins
// Auto-create a plugin manifest, so we can disable it, if it fails to load. // Auto-create a plugin manifest, so we can disable it, if it fails to load.
manifest = new PluginManifest manifest = new PluginManifest
{ {
Status = PluginStatus.Restart, Status = PluginStatus.Active,
Name = metafile, Name = metafile,
AutoUpdate = false, AutoUpdate = false,
Id = metafile.GetMD5(), Id = metafile.GetMD5(),

View file

@ -143,21 +143,21 @@ namespace Emby.Server.Implementations.ScheduledTasks
{ {
if (File.Exists(path)) if (File.Exists(path))
{ {
try var bytes = File.ReadAllBytes(path);
if (bytes.Length > 0)
{ {
var jsonString = File.ReadAllText(path, Encoding.UTF8); try
if (!string.IsNullOrWhiteSpace(jsonString))
{ {
_lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(jsonString, _jsonOptions); _lastExecutionResult = JsonSerializer.Deserialize<TaskResult>(bytes, _jsonOptions);
} }
else catch (JsonException ex)
{ {
_logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path); _logger.LogError(ex, "Error deserializing {File}", path);
} }
} }
catch (Exception ex) else
{ {
_logger.LogError(ex, "Error deserializing {File}", path); _logger.LogDebug("Scheduled Task history file {Path} is empty. Skipping deserialization.", path);
} }
} }
@ -177,7 +177,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
lock (_lastExecutionResultSyncLock) lock (_lastExecutionResultSyncLock)
{ {
using FileStream createStream = File.OpenWrite(path); using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
JsonSerializer.SerializeAsync(createStream, value, _jsonOptions); JsonSerializer.SerializeAsync(createStream, value, _jsonOptions);
} }
} }
@ -541,8 +541,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
TaskTriggerInfo[] list = null; TaskTriggerInfo[] list = null;
if (File.Exists(path)) if (File.Exists(path))
{ {
var jsonString = File.ReadAllText(path, Encoding.UTF8); var bytes = File.ReadAllBytes(path);
list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(jsonString, _jsonOptions); list = JsonSerializer.Deserialize<TaskTriggerInfo[]>(bytes, _jsonOptions);
} }
// Return defaults if file doesn't exist. // Return defaults if file doesn't exist.
@ -577,9 +577,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
var path = GetConfigurationFilePath(); var path = GetConfigurationFilePath();
Directory.CreateDirectory(Path.GetDirectoryName(path)); Directory.CreateDirectory(Path.GetDirectoryName(path));
using FileStream createStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
var json = JsonSerializer.Serialize(triggers, _jsonOptions); JsonSerializer.SerializeAsync(createStream, triggers, _jsonOptions);
File.WriteAllText(path, json, Encoding.UTF8);
} }
/// <summary> /// <summary>

View file

@ -143,7 +143,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
Directory.CreateDirectory(parentPath); Directory.CreateDirectory(parentPath);
string text = string.Join("|", previouslyFailedImages); string text = string.Join('|', previouslyFailedImages);
File.WriteAllText(failHistoryPath, text); File.WriteAllText(failHistoryPath, text);
} }

View file

@ -80,10 +80,11 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
// Delete log files more than n days old // Delete log files more than n days old
var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays); var minDateModified = DateTime.UtcNow.AddDays(-_configurationManager.CommonConfiguration.LogFileRetentionDays);
// Only delete the .txt log files, the *.log files created by serilog get managed by itself // Only delete files that serilog doesn't manage (anything that doesn't start with 'log_'
var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, new[] { ".txt" }, true, true) var filesToDelete = _fileSystem.GetFiles(_configurationManager.CommonApplicationPaths.LogDirectoryPath, true)
.Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified) .Where(f => !f.Name.StartsWith("log_", StringComparison.Ordinal)
.ToList(); && _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
.ToList();
var index = 0; var index = 0;

View file

@ -50,7 +50,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
var dueTime = triggerDate - now; var dueTime = triggerDate - now;
logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:g}, which is {DueTime:g} from now.", taskName, triggerDate, dueTime); logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1)); Timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
} }

View file

@ -1456,7 +1456,12 @@ namespace Emby.Server.Implementations.Session
throw new SecurityException("Unknown quick connect token"); throw new SecurityException("Unknown quick connect token");
} }
request.UserId = result.Items[0].UserId; var info = result.Items[0];
request.UserId = info.UserId;
// There's no need to keep the quick connect token in the database, as AuthenticateNewSessionInternal() issues a long lived token.
_authRepo.Delete(info);
return AuthenticateNewSessionInternal(request, false); return AuthenticateNewSessionInternal(request, false);
} }

View file

@ -143,10 +143,31 @@ namespace Emby.Server.Implementations.TV
var allNextUp = seriesKeys var allNextUp = seriesKeys
.Select(i => GetNextUp(i, currentUser, dtoOptions)); .Select(i => GetNextUp(i, currentUser, dtoOptions));
// If viewing all next up for all series, remove first episodes
// But if that returns empty, keep those first episodes (avoid completely empty view)
var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
var anyFound = false;
return allNextUp return allNextUp
.Where(i => .Where(i =>
{ {
return i.Item1 != DateTime.MinValue; if (request.DisableFirstEpisode)
{
return i.Item1 != DateTime.MinValue;
}
if (alwaysEnableFirstEpisode || i.Item1 != DateTime.MinValue)
{
anyFound = true;
return true;
}
if (!anyFound && i.Item1 == DateTime.MinValue)
{
return true;
}
return false;
}) })
.Select(i => i.Item2()) .Select(i => i.Item2())
.Where(i => i != null); .Where(i => i != null);

View file

@ -0,0 +1,28 @@
using System;
namespace Jellyfin.Api.Attributes
{
/// <summary>
/// Internal produces image attribute.
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class AcceptsFileAttribute : Attribute
{
private readonly string[] _contentTypes;
/// <summary>
/// Initializes a new instance of the <see cref="AcceptsFileAttribute"/> class.
/// </summary>
/// <param name="contentTypes">Content types this endpoint produces.</param>
public AcceptsFileAttribute(params string[] contentTypes)
{
_contentTypes = contentTypes;
}
/// <summary>
/// Gets the configured content types.
/// </summary>
/// <returns>the configured content types.</returns>
public string[] GetContentTypes() => _contentTypes;
}
}

View file

@ -0,0 +1,18 @@
namespace Jellyfin.Api.Attributes
{
/// <summary>
/// Produces file attribute of "image/*".
/// </summary>
public class AcceptsImageFileAttribute : AcceptsFileAttribute
{
private const string ContentType = "image/*";
/// <summary>
/// Initializes a new instance of the <see cref="AcceptsImageFileAttribute"/> class.
/// </summary>
public AcceptsImageFileAttribute()
: base(ContentType)
{
}
}
}

View file

@ -0,0 +1,12 @@
using System;
namespace Jellyfin.Api.Attributes
{
/// <summary>
/// Attribute to mark a parameter as obsolete.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class ParameterObsoleteAttribute : Attribute
{
}
}

View file

@ -3,8 +3,10 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -88,8 +90,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@ -127,8 +129,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,
@ -287,8 +289,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
@ -326,8 +328,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,

View file

@ -1,13 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -121,9 +120,9 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] string? sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)

View file

@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View file

@ -7,17 +7,11 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Models; using Jellyfin.Api.Models;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers namespace Jellyfin.Api.Controllers
{ {
@ -51,7 +45,6 @@ namespace Jellyfin.Api.Controllers
/// Gets the configuration pages. /// Gets the configuration pages.
/// </summary> /// </summary>
/// <param name="enableInMainMenu">Whether to enable in the main menu.</param> /// <param name="enableInMainMenu">Whether to enable in the main menu.</param>
/// <param name="pageType">The <see cref="ConfigurationPageInfo"/>.</param>
/// <response code="200">ConfigurationPages returned.</response> /// <response code="200">ConfigurationPages returned.</response>
/// <response code="404">Server still loading.</response> /// <response code="404">Server still loading.</response>
/// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns> /// <returns>An <see cref="IEnumerable{ConfigurationPageInfo}"/> with infos about the plugins.</returns>
@ -59,40 +52,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages( public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages(
[FromQuery] bool? enableInMainMenu, [FromQuery] bool? enableInMainMenu)
[FromQuery] ConfigurationPageType? pageType)
{ {
const string unavailableMessage = "The server is still loading. Please try again momentarily."; var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList();
var pages = _appHost.GetExports<IPluginConfigurationPage>().ToList();
if (pages == null)
{
return NotFound(unavailableMessage);
}
// Don't allow a failing plugin to fail them all
var configPages = pages.Select(p =>
{
try
{
return new ConfigurationPageInfo(p);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting plugin information from {Plugin}", p.GetType().Name);
return null;
}
})
.Where(i => i != null)
.ToList();
configPages.AddRange(_pluginManager.Plugins.SelectMany(GetConfigPages));
if (pageType.HasValue)
{
configPages = configPages.Where(p => p!.ConfigurationPageType == pageType).ToList();
}
if (enableInMainMenu.HasValue) if (enableInMainMenu.HasValue)
{ {
@ -121,24 +83,14 @@ namespace Jellyfin.Api.Controllers
var isJs = false; var isJs = false;
var isTemplate = false; var isTemplate = false;
var page = _appHost.GetExports<IPluginConfigurationPage>().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
if (page != null) if (altPage != null)
{ {
plugin = page.Plugin; plugin = altPage.Item2;
stream = page.GetHtmlStream(); stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
}
if (plugin == null) isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
{ isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
if (altPage != null)
{
plugin = altPage.Item2;
stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
}
} }
if (plugin != null && stream != null) if (plugin != null && stream != null)

View file

@ -15,7 +15,6 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos; using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dlna;

View file

@ -1,13 +1,12 @@
using System; using System;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -51,7 +50,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy( public ActionResult<QueryFiltersLegacy> GetQueryFiltersLegacy(
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes)
{ {
var user = userId.HasValue && !userId.Equals(Guid.Empty) var user = userId.HasValue && !userId.Equals(Guid.Empty)
@ -60,10 +59,10 @@ namespace Jellyfin.Api.Controllers
BaseItem? item = null; BaseItem? item = null;
if (includeItemTypes.Length != 1 if (includeItemTypes.Length != 1
|| !(string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase) || !(includeItemTypes[0] == BaseItemKind.BoxSet
|| string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.Playlist
|| string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.Trailer
|| string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase))) || includeItemTypes[0] == BaseItemKind.Program))
{ {
item = _libraryManager.GetParentItem(parentId, user?.Id); item = _libraryManager.GetParentItem(parentId, user?.Id);
} }
@ -72,7 +71,7 @@ namespace Jellyfin.Api.Controllers
{ {
User = user, User = user,
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
Recursive = true, Recursive = true,
EnableTotalRecordCount = false, EnableTotalRecordCount = false,
DtoOptions = new DtoOptions DtoOptions = new DtoOptions
@ -137,7 +136,7 @@ namespace Jellyfin.Api.Controllers
public ActionResult<QueryFilters> GetQueryFilters( public ActionResult<QueryFilters> GetQueryFilters(
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isAiring, [FromQuery] bool? isAiring,
[FromQuery] bool? isMovie, [FromQuery] bool? isMovie,
[FromQuery] bool? isSports, [FromQuery] bool? isSports,
@ -152,10 +151,10 @@ namespace Jellyfin.Api.Controllers
BaseItem? parentItem = null; BaseItem? parentItem = null;
if (includeItemTypes.Length == 1 if (includeItemTypes.Length == 1
&& (string.Equals(includeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase) && (includeItemTypes[0] == BaseItemKind.BoxSet
|| string.Equals(includeItemTypes[0], nameof(Playlist), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.Playlist
|| string.Equals(includeItemTypes[0], nameof(Trailer), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.Trailer
|| string.Equals(includeItemTypes[0], "Program", StringComparison.OrdinalIgnoreCase))) || includeItemTypes[0] == BaseItemKind.Program))
{ {
parentItem = null; parentItem = null;
} }
@ -167,7 +166,7 @@ namespace Jellyfin.Api.Controllers
var filters = new QueryFilters(); var filters = new QueryFilters();
var genreQuery = new InternalItemsQuery(user) var genreQuery = new InternalItemsQuery(user)
{ {
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
DtoOptions = new DtoOptions DtoOptions = new DtoOptions
{ {
Fields = Array.Empty<ItemFields>(), Fields = Array.Empty<ItemFields>(),
@ -192,10 +191,10 @@ namespace Jellyfin.Api.Controllers
} }
if (includeItemTypes.Length == 1 if (includeItemTypes.Length == 1
&& (string.Equals(includeItemTypes[0], nameof(MusicAlbum), StringComparison.OrdinalIgnoreCase) && (includeItemTypes[0] == BaseItemKind.MusicAlbum
|| string.Equals(includeItemTypes[0], nameof(MusicVideo), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.MusicVideo
|| string.Equals(includeItemTypes[0], nameof(MusicArtist), StringComparison.OrdinalIgnoreCase) || includeItemTypes[0] == BaseItemKind.MusicArtist
|| string.Equals(includeItemTypes[0], nameof(Audio), StringComparison.OrdinalIgnoreCase))) || includeItemTypes[0] == BaseItemKind.Audio))
{ {
filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
{ {

View file

@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,
IsFavorite = isFavorite, IsFavorite = isFavorite,

View file

@ -2,13 +2,11 @@ using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;

View file

@ -87,6 +87,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}")] [HttpPost("Users/{userId}/Images/{imageType}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@ -133,6 +134,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}/{index}")] [HttpPost("Users/{userId}/Images/{imageType}/{index}")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
@ -312,6 +314,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("Items/{itemId}/Images/{imageType}")] [HttpPost("Items/{itemId}/Images/{imageType}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
@ -346,6 +349,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns> /// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}")] [HttpPost("Items/{itemId}/Images/{imageType}/{imageIndex}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]

View file

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;

View file

@ -2,8 +2,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.IO; using System.IO;
using System.Linq;
using System.Net.Mime;
using System.Text.Json; using System.Text.Json;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;

View file

@ -1,6 +1,5 @@
using System; using System;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
@ -175,16 +174,16 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] bool? recursive, [FromQuery] bool? recursive,
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] string? sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
[FromQuery] string? sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? isPlayed, [FromQuery] bool? isPlayed,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
@ -233,8 +232,8 @@ namespace Jellyfin.Api.Controllers
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
if (includeItemTypes.Length == 1 if (includeItemTypes.Length == 1
&& (includeItemTypes[0].Equals("Playlist", StringComparison.OrdinalIgnoreCase) && (includeItemTypes[0] == BaseItemKind.Playlist
|| includeItemTypes[0].Equals("BoxSet", StringComparison.OrdinalIgnoreCase))) || includeItemTypes[0] == BaseItemKind.BoxSet))
{ {
parentId = null; parentId = null;
} }
@ -251,7 +250,7 @@ namespace Jellyfin.Api.Controllers
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)) && string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
{ {
recursive = true; recursive = true;
includeItemTypes = new[] { "Playlist" }; includeItemTypes = new[] { BaseItemKind.Playlist };
} }
var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels); var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
@ -286,8 +285,8 @@ namespace Jellyfin.Api.Controllers
{ {
IsPlayed = isPlayed, IsPlayed = isPlayed,
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
Recursive = recursive ?? false, Recursive = recursive ?? false,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder), OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
IsFavorite = isFavorite, IsFavorite = isFavorite,
@ -608,16 +607,16 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] bool? recursive, [FromQuery] bool? recursive,
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] string? sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] imageTypes,
[FromQuery] string? sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] bool? isPlayed, [FromQuery] bool? isPlayed,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] officialRatings,
@ -773,8 +772,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? enableUserData, [FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool enableTotalRecordCount = true, [FromQuery] bool enableTotalRecordCount = true,
[FromQuery] bool? enableImages = true) [FromQuery] bool? enableImages = true)
{ {
@ -810,8 +809,8 @@ namespace Jellyfin.Api.Controllers
CollapseBoxSetItems = false, CollapseBoxSetItems = false,
EnableTotalRecordCount = enableTotalRecordCount, EnableTotalRecordCount = enableTotalRecordCount,
AncestorIds = ancestorIds, AncestorIds = ancestorIds,
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
SearchTerm = searchTerm SearchTerm = searchTerm
}); });

View file

@ -11,7 +11,6 @@ using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Api.Models.LibraryDtos;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;

View file

@ -553,8 +553,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isSports, [FromQuery] bool? isSports,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? sortBy, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery] string? sortOrder, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres, [FromQuery, ModelBinder(typeof(PipeDelimitedArrayModelBinder))] string[] genres,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] genreIds,
[FromQuery] bool? enableImages, [FromQuery] bool? enableImages,

View file

@ -83,6 +83,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence. /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
/// Query parameters are obsolete.
/// </remarks> /// </remarks>
/// <param name="itemId">The item id.</param> /// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>
@ -106,20 +107,20 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo( public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery, ParameterObsolete] Guid? userId,
[FromQuery] int? maxStreamingBitrate, [FromQuery, ParameterObsolete] int? maxStreamingBitrate,
[FromQuery] long? startTimeTicks, [FromQuery, ParameterObsolete] long? startTimeTicks,
[FromQuery] int? audioStreamIndex, [FromQuery, ParameterObsolete] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex, [FromQuery, ParameterObsolete] int? subtitleStreamIndex,
[FromQuery] int? maxAudioChannels, [FromQuery, ParameterObsolete] int? maxAudioChannels,
[FromQuery] string? mediaSourceId, [FromQuery, ParameterObsolete] string? mediaSourceId,
[FromQuery] string? liveStreamId, [FromQuery, ParameterObsolete] string? liveStreamId,
[FromQuery] bool? autoOpenLiveStream, [FromQuery, ParameterObsolete] bool? autoOpenLiveStream,
[FromQuery] bool? enableDirectPlay, [FromQuery, ParameterObsolete] bool? enableDirectPlay,
[FromQuery] bool? enableDirectStream, [FromQuery, ParameterObsolete] bool? enableDirectStream,
[FromQuery] bool? enableTranscoding, [FromQuery, ParameterObsolete] bool? enableTranscoding,
[FromQuery] bool? allowVideoStreamCopy, [FromQuery, ParameterObsolete] bool? allowVideoStreamCopy,
[FromQuery] bool? allowAudioStreamCopy, [FromQuery, ParameterObsolete] bool? allowAudioStreamCopy,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto) [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] PlaybackInfoDto? playbackInfoDto)
{ {
var authInfo = _authContext.GetAuthorizationInfo(Request); var authInfo = _authContext.GetAuthorizationInfo(Request);

View file

@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -74,8 +75,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,
IsFavorite = isFavorite, IsFavorite = isFavorite,

View file

@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Common.Json;
using MediaBrowser.Common.Updates; using MediaBrowser.Common.Updates;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Model.Updates; using MediaBrowser.Model.Updates;
@ -158,7 +157,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Repositories")] [HttpPost("Repositories")]
[Authorize(Policy = Policies.DefaultAuthorization)] [Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult SetRepositories([FromBody] List<RepositoryInfo> repositoryInfos) public ActionResult SetRepositories([FromBody, Required] List<RepositoryInfo> repositoryInfos)
{ {
_serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos; _serverConfigurationManager.Configuration.PluginRepositories = repositoryInfos;
_serverConfigurationManager.SaveConfiguration(); _serverConfigurationManager.SaveConfiguration();

View file

@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;

View file

@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.PlaylistDtos; using Jellyfin.Api.Models.PlaylistDtos;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
@ -57,6 +57,7 @@ namespace Jellyfin.Api.Controllers
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence. /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
/// Query parameters are obsolete.
/// </remarks> /// </remarks>
/// <param name="name">The playlist name.</param> /// <param name="name">The playlist name.</param>
/// <param name="ids">The item ids.</param> /// <param name="ids">The item ids.</param>
@ -70,10 +71,10 @@ namespace Jellyfin.Api.Controllers
[HttpPost] [HttpPost]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist( public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
[FromQuery] string? name, [FromQuery, ParameterObsolete] string? name,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] IReadOnlyList<Guid> ids, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
[FromQuery] Guid? userId, [FromQuery, ParameterObsolete] Guid? userId,
[FromQuery] string? mediaType, [FromQuery, ParameterObsolete] string? mediaType,
[FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest) [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest)
{ {
if (ids.Count == 0) if (ids.Count == 0)

View file

@ -1,10 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net.Mime;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
@ -300,9 +298,7 @@ namespace Jellyfin.Api.Controllers
} }
var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty);
if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages if (plugin.Manifest.ImagePath == null || !System.IO.File.Exists(imagePath))
|| plugin.Manifest.ImagePath == null
|| !System.IO.File.Exists(imagePath))
{ {
return NotFound(); return NotFound();
} }

View file

@ -6,6 +6,7 @@ using System.Linq;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -83,8 +84,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery, Required] string searchTerm, [FromQuery, Required] string searchTerm,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery] bool? isMovie, [FromQuery] bool? isMovie,
@ -109,8 +110,8 @@ namespace Jellyfin.Api.Controllers
IncludeStudios = includeStudios, IncludeStudios = includeStudios,
StartIndex = startIndex, StartIndex = startIndex,
UserId = userId ?? Guid.Empty, UserId = userId ?? Guid.Empty,
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
MediaTypes = mediaTypes, MediaTypes = mediaTypes,
ParentId = parentId, ParentId = parentId,

View file

@ -5,6 +5,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -73,8 +74,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? searchTerm, [FromQuery] string? searchTerm,
[FromQuery] Guid? parentId, [FromQuery] Guid? parentId,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] excludeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] excludeItemTypes,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] includeItemTypes, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] bool? enableUserData, [FromQuery] bool? enableUserData,
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
@ -96,8 +97,8 @@ namespace Jellyfin.Api.Controllers
var query = new InternalItemsQuery(user) var query = new InternalItemsQuery(user)
{ {
ExcludeItemTypes = excludeItemTypes, ExcludeItemTypes = RequestHelpers.GetItemTypeStrings(excludeItemTypes),
IncludeItemTypes = includeItemTypes, IncludeItemTypes = RequestHelpers.GetItemTypeStrings(includeItemTypes),
StartIndex = startIndex, StartIndex = startIndex,
Limit = limit, Limit = limit,
IsFavorite = isFavorite, IsFavorite = isFavorite,

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