mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Merge branch 'master' into network-rewrite
This commit is contained in:
commit
80b8661008
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
@ -27,11 +27,11 @@ jobs:
|
|||||||
dotnet-version: '7.0.x'
|
dotnet-version: '7.0.x'
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: +security-extended
|
queries: +security-extended
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2
|
uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2
|
||||||
|
2
.github/workflows/openapi.yml
vendored
2
.github/workflows/openapi.yml
vendored
@ -103,7 +103,7 @@ jobs:
|
|||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
echo ::set-output name=body::$body
|
echo ::set-output name=body::$body
|
||||||
- name: Find difference comment
|
- name: Find difference comment
|
||||||
uses: peter-evans/find-comment@85a676a52594b4481e0532825a2d8906ef96dac2 # v2
|
uses: peter-evans/find-comment@034abe94d3191f9c89d870519735beae326f2bdb # v2
|
||||||
id: find-comment
|
id: find-comment
|
||||||
with:
|
with:
|
||||||
issue-number: ${{ github.event.pull_request.number }}
|
issue-number: ${{ github.event.pull_request.number }}
|
||||||
|
@ -163,6 +163,7 @@
|
|||||||
- [vgambier](https://github.com/vgambier)
|
- [vgambier](https://github.com/vgambier)
|
||||||
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
- [MinecraftPlaye](https://github.com/MinecraftPlaye)
|
||||||
- [RealGreenDragon](https://github.com/RealGreenDragon)
|
- [RealGreenDragon](https://github.com/RealGreenDragon)
|
||||||
|
- [ipitio](https://github.com/ipitio)
|
||||||
|
|
||||||
# Emby Contributors
|
# Emby Contributors
|
||||||
|
|
||||||
|
@ -6,9 +6,9 @@
|
|||||||
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
<!-- Run "dotnet list package (dash,dash)outdated" to see the latest versions of each package.-->
|
||||||
|
|
||||||
<ItemGroup Label="Package Dependencies">
|
<ItemGroup Label="Package Dependencies">
|
||||||
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture.AutoMoq" Version="4.18.0" />
|
||||||
<PackageVersion Include="AutoFixture.Xunit2" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture.Xunit2" Version="4.18.0" />
|
||||||
<PackageVersion Include="AutoFixture" Version="4.17.0" />
|
<PackageVersion Include="AutoFixture" Version="4.18.0" />
|
||||||
<PackageVersion Include="BDInfo" Version="0.7.6.2" />
|
<PackageVersion Include="BDInfo" Version="0.7.6.2" />
|
||||||
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
<PackageVersion Include="BlurHashSharp.SkiaSharp" Version="1.2.0" />
|
||||||
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
<PackageVersion Include="BlurHashSharp" Version="1.2.0" />
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<PackageVersion Include="Diacritics" Version="3.3.14" />
|
<PackageVersion Include="Diacritics" Version="3.3.14" />
|
||||||
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
<PackageVersion Include="DiscUtils.Udf" Version="0.16.13" />
|
||||||
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
<PackageVersion Include="DotNet.Glob" Version="3.1.3" />
|
||||||
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.3" />
|
<PackageVersion Include="EFCoreSecondLevelCacheInterceptor" Version="3.8.5" />
|
||||||
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
<PackageVersion Include="FsCheck.Xunit" Version="2.16.5" />
|
||||||
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
<PackageVersion Include="Jellyfin.XmlTv" Version="10.8.0" />
|
||||||
<PackageVersion Include="libse" Version="3.6.10" />
|
<PackageVersion Include="libse" Version="3.6.10" />
|
||||||
@ -46,7 +46,7 @@
|
|||||||
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
<PackageVersion Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||||
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
<PackageVersion Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="1.1.1" />
|
||||||
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
<PackageVersion Include="MimeTypes" Version="2.4.0" />
|
||||||
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
<PackageVersion Include="Mono.Nat" Version="3.0.4" />
|
||||||
|
@ -27,7 +27,7 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
|
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
|
||||||
private static IEnumerable<StateVariable> GetStateVariables()
|
private static IEnumerable<StateVariable> GetStateVariables()
|
||||||
{
|
{
|
||||||
var list = new List<StateVariable>
|
return new StateVariable[]
|
||||||
{
|
{
|
||||||
new StateVariable
|
new StateVariable
|
||||||
{
|
{
|
||||||
@ -114,8 +114,6 @@ namespace Emby.Dlna.ConnectionManager
|
|||||||
SendsEvents = false
|
SendsEvents = false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
|
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
|
||||||
private static IEnumerable<StateVariable> GetStateVariables()
|
private static IEnumerable<StateVariable> GetStateVariables()
|
||||||
{
|
{
|
||||||
var list = new List<StateVariable>
|
return new StateVariable[]
|
||||||
{
|
{
|
||||||
new StateVariable
|
new StateVariable
|
||||||
{
|
{
|
||||||
@ -154,8 +154,6 @@ namespace Emby.Dlna.ContentDirectory
|
|||||||
SendsEvents = false
|
SendsEvents = false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,11 +147,16 @@ namespace Emby.Dlna.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFriendlyName()
|
internal string GetFriendlyName()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_profile.FriendlyName))
|
if (string.IsNullOrEmpty(_profile.FriendlyName))
|
||||||
{
|
{
|
||||||
return "Jellyfin - " + _serverName;
|
return _serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_profile.FriendlyName.Contains("${HostName}", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return _profile.FriendlyName;
|
||||||
}
|
}
|
||||||
|
|
||||||
var characterList = new List<char>();
|
var characterList = new List<char>();
|
||||||
@ -164,13 +169,18 @@ namespace Emby.Dlna.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var characters = characterList.ToArray();
|
var serverName = string.Create(
|
||||||
|
characterList.Count,
|
||||||
|
characterList,
|
||||||
|
(dest, source) =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < dest.Length; i++)
|
||||||
|
{
|
||||||
|
dest[i] = source[i];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var serverName = new string(characters);
|
return _profile.FriendlyName.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var name = _profile.FriendlyName?.Replace("${HostName}", serverName, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
return name ?? string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppendIconList(StringBuilder builder)
|
private void AppendIconList(StringBuilder builder)
|
||||||
|
@ -32,7 +32,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
var fileName = Path.GetFileNameWithoutExtension(path);
|
var fileName = Path.GetFileNameWithoutExtension(path);
|
||||||
foreach (var expression in _options.AudioBookPartsExpressions)
|
foreach (var expression in _options.AudioBookPartsExpressions)
|
||||||
{
|
{
|
||||||
var match = new Regex(expression, RegexOptions.IgnoreCase).Match(fileName);
|
var match = Regex.Match(fileName, expression, RegexOptions.IgnoreCase);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
if (!result.ChapterNumber.HasValue)
|
if (!result.ChapterNumber.HasValue)
|
||||||
|
@ -79,25 +79,25 @@ namespace Emby.Naming.AudioBook
|
|||||||
{
|
{
|
||||||
if (group.Count() > 1 || haveChaptersOrPages)
|
if (group.Count() > 1 || haveChaptersOrPages)
|
||||||
{
|
{
|
||||||
var ex = new List<AudioBookFileInfo>();
|
List<AudioBookFileInfo>? ex = null;
|
||||||
var alt = new List<AudioBookFileInfo>();
|
List<AudioBookFileInfo>? alt = null;
|
||||||
|
|
||||||
foreach (var audioFile in group)
|
foreach (var audioFile in group)
|
||||||
{
|
{
|
||||||
var name = Path.GetFileNameWithoutExtension(audioFile.Path);
|
var name = Path.GetFileNameWithoutExtension(audioFile.Path.AsSpan());
|
||||||
if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase) ||
|
if (name.Equals("audiobook", StringComparison.OrdinalIgnoreCase)
|
||||||
name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase) ||
|
|| name.Contains(nameParserResult.Name, StringComparison.OrdinalIgnoreCase)
|
||||||
name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
|
|| name.Contains(nameWithReplacedDots, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
alt.Add(audioFile);
|
(alt ??= new()).Add(audioFile);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ex.Add(audioFile);
|
(ex ??= new()).Add(audioFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ex.Count > 0)
|
if (ex is not null)
|
||||||
{
|
{
|
||||||
var extra = ex
|
var extra = ex
|
||||||
.OrderBy(x => x.Container)
|
.OrderBy(x => x.Container)
|
||||||
@ -108,7 +108,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
extras.AddRange(extra);
|
extras.AddRange(extra);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alt.Count > 0)
|
if (alt is not null)
|
||||||
{
|
{
|
||||||
var alternatives = alt
|
var alternatives = alt
|
||||||
.OrderBy(x => x.Container)
|
.OrderBy(x => x.Container)
|
||||||
|
@ -30,7 +30,7 @@ namespace Emby.Naming.AudioBook
|
|||||||
AudioBookNameParserResult result = default;
|
AudioBookNameParserResult result = default;
|
||||||
foreach (var expression in _options.AudioBookNamesExpressions)
|
foreach (var expression in _options.AudioBookNamesExpressions)
|
||||||
{
|
{
|
||||||
var match = new Regex(expression, RegexOptions.IgnoreCase).Match(name);
|
var match = Regex.Match(name, expression, RegexOptions.IgnoreCase);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
if (result.Name is null)
|
if (result.Name is null)
|
||||||
|
@ -14,7 +14,7 @@ namespace Emby.Naming.TV
|
|||||||
/// Used for removing separators between words, i.e turns "The_show" into "The show" while
|
/// Used for removing separators between words, i.e turns "The_show" into "The show" while
|
||||||
/// preserving namings like "S.H.O.W".
|
/// preserving namings like "S.H.O.W".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))");
|
private static readonly Regex _seriesNameRegex = new Regex(@"((?<a>[^\._]{2,})[\._]*)|([\._](?<b>[^\._]{2,}))", RegexOptions.Compiled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolve information about series from path.
|
/// Resolve information about series from path.
|
||||||
|
@ -17,7 +17,7 @@ public class FileStackRule
|
|||||||
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
|
/// <param name="isNumerical">Whether the file stack rule uses numerical or alphabetical numbering.</param>
|
||||||
public FileStackRule(string token, bool isNumerical)
|
public FileStackRule(string token, bool isNumerical)
|
||||||
{
|
{
|
||||||
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase);
|
_tokenRegex = new Regex(token, RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
IsNumerical = isNumerical;
|
IsNumerical = isNumerical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
|
using Jellyfin.Extensions;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
|
||||||
namespace Emby.Naming.Video
|
namespace Emby.Naming.Video
|
||||||
@ -13,6 +14,8 @@ namespace Emby.Naming.Video
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class VideoListResolver
|
public static class VideoListResolver
|
||||||
{
|
{
|
||||||
|
private static readonly Regex _resolutionRegex = new Regex("[0-9]{2}[0-9]+[ip]", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resolves alternative versions and extras from list of video files.
|
/// Resolves alternative versions and extras from list of video files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -115,19 +118,34 @@ namespace Emby.Naming.Video
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsEligibleForMultiVersion(folderName, video.Files[0].Path, namingOptions))
|
if (!IsEligibleForMultiVersion(folderName, video.Files[0].FileNameWithoutExtension, namingOptions))
|
||||||
{
|
{
|
||||||
return videos;
|
return videos;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (folderName.Equals(Path.GetFileNameWithoutExtension(video.Files[0].Path.AsSpan()), StringComparison.Ordinal))
|
if (folderName.Equals(video.Files[0].FileNameWithoutExtension, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
primary = video;
|
primary = video;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The list is created and overwritten in the caller, so we are allowed to do in-place sorting
|
if (videos.Count > 1)
|
||||||
videos.Sort((x, y) => string.Compare(x.Name, y.Name, StringComparison.Ordinal));
|
{
|
||||||
|
var groups = videos.GroupBy(x => _resolutionRegex.IsMatch(x.Files[0].FileNameWithoutExtension)).ToList();
|
||||||
|
videos.Clear();
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
if (group.Key)
|
||||||
|
{
|
||||||
|
videos.InsertRange(0, group.OrderByDescending(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
videos.AddRange(group.OrderBy(x => x.Files[0].FileNameWithoutExtension.ToString(), new AlphanumericComparator()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
primary ??= videos[0];
|
primary ??= videos[0];
|
||||||
videos.Remove(primary);
|
videos.Remove(primary);
|
||||||
|
|
||||||
@ -161,9 +179,8 @@ namespace Emby.Naming.Video
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, string testFilePath, NamingOptions namingOptions)
|
private static bool IsEligibleForMultiVersion(ReadOnlySpan<char> folderName, ReadOnlySpan<char> testFilename, NamingOptions namingOptions)
|
||||||
{
|
{
|
||||||
var testFilename = Path.GetFileNameWithoutExtension(testFilePath.AsSpan());
|
|
||||||
if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
|
if (!testFilename.StartsWith(folderName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
@ -401,7 +401,7 @@ namespace Emby.Server.Implementations.Channels
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
results = new List<MediaSourceInfo>();
|
results = Enumerable.Empty<MediaSourceInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -206,8 +206,7 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
throw new ArgumentException("No collection exists with the supplied Id");
|
throw new ArgumentException("No collection exists with the supplied Id");
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = new List<LinkedChild>();
|
List<BaseItem>? itemList = null;
|
||||||
var itemList = new List<BaseItem>();
|
|
||||||
|
|
||||||
var linkedChildrenList = collection.GetLinkedChildren();
|
var linkedChildrenList = collection.GetLinkedChildren();
|
||||||
var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList();
|
var currentLinkedChildrenIds = linkedChildrenList.Select(i => i.Id).ToList();
|
||||||
@ -223,18 +222,23 @@ namespace Emby.Server.Implementations.Collections
|
|||||||
|
|
||||||
if (!currentLinkedChildrenIds.Contains(id))
|
if (!currentLinkedChildrenIds.Contains(id))
|
||||||
{
|
{
|
||||||
itemList.Add(item);
|
(itemList ??= new()).Add(item);
|
||||||
|
|
||||||
list.Add(LinkedChild.Create(item));
|
|
||||||
linkedChildrenList.Add(item);
|
linkedChildrenList.Add(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (list.Count > 0)
|
if (itemList is not null)
|
||||||
{
|
{
|
||||||
LinkedChild[] newChildren = new LinkedChild[collection.LinkedChildren.Length + list.Count];
|
var originalLen = collection.LinkedChildren.Length;
|
||||||
|
var newItemCount = itemList.Count;
|
||||||
|
LinkedChild[] newChildren = new LinkedChild[originalLen + newItemCount];
|
||||||
collection.LinkedChildren.CopyTo(newChildren, 0);
|
collection.LinkedChildren.CopyTo(newChildren, 0);
|
||||||
list.CopyTo(newChildren, collection.LinkedChildren.Length);
|
for (int i = 0; i < newItemCount; i++)
|
||||||
|
{
|
||||||
|
newChildren[originalLen + i] = LinkedChild.Create(itemList[i]);
|
||||||
|
}
|
||||||
|
|
||||||
collection.LinkedChildren = newChildren;
|
collection.LinkedChildren = newChildren;
|
||||||
collection.UpdateRatingToItems(linkedChildrenList);
|
collection.UpdateRatingToItems(linkedChildrenList);
|
||||||
|
|
||||||
|
@ -586,7 +586,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
/// <exception cref="ArgumentNullException">
|
/// <exception cref="ArgumentNullException">
|
||||||
/// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>.
|
/// <paramref name="items"/> or <paramref name="cancellationToken"/> is <c>null</c>.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
|
public void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(items);
|
ArgumentNullException.ThrowIfNull(items);
|
||||||
|
|
||||||
@ -594,9 +594,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
|
||||||
var tuples = new List<(BaseItem, List<Guid>, BaseItem, string, List<string>)>();
|
var itemsLen = items.Count;
|
||||||
foreach (var item in items)
|
var tuples = new ValueTuple<BaseItem, List<Guid>, BaseItem, string, List<string>>[itemsLen];
|
||||||
|
for (int i = 0; i < itemsLen; i++)
|
||||||
{
|
{
|
||||||
|
var item = items[i];
|
||||||
var ancestorIds = item.SupportsAncestors ?
|
var ancestorIds = item.SupportsAncestors ?
|
||||||
item.GetAncestorIds().Distinct().ToList() :
|
item.GetAncestorIds().Distinct().ToList() :
|
||||||
null;
|
null;
|
||||||
@ -606,7 +608,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
|
var userdataKey = item.GetUserDataKeys().FirstOrDefault();
|
||||||
var inheritedTags = item.GetInheritedTags();
|
var inheritedTags = item.GetInheritedTags();
|
||||||
|
|
||||||
tuples.Add((item, ancestorIds, topParent, userdataKey, inheritedTags));
|
tuples[i] = (item, ancestorIds, topParent, userdataKey, inheritedTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var connection = GetConnection())
|
using (var connection = GetConnection())
|
||||||
@ -3202,7 +3204,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
return IsAlphaNumeric(value);
|
return IsAlphaNumeric(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
|
#nullable enable
|
||||||
|
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement? statement)
|
||||||
{
|
{
|
||||||
if (query.IsResumable ?? false)
|
if (query.IsResumable ?? false)
|
||||||
{
|
{
|
||||||
@ -3677,7 +3680,6 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (statement is not null)
|
if (statement is not null)
|
||||||
{
|
{
|
||||||
nameContains = FixUnicodeChars(nameContains);
|
nameContains = FixUnicodeChars(nameContains);
|
||||||
|
|
||||||
statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
|
statement.TryBind("@NameContains", "%" + GetCleanValue(nameContains) + "%");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3803,13 +3805,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var artistId in query.ArtistIds)
|
foreach (var artistId in query.ArtistIds)
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, artistId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, artistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3824,13 +3821,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var artistId in query.AlbumArtistIds)
|
foreach (var artistId in query.AlbumArtistIds)
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=1))");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, artistId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, artistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3845,13 +3837,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var artistId in query.ContributingArtistIds)
|
foreach (var artistId in query.ContributingArtistIds)
|
||||||
{
|
{
|
||||||
var paramName = "@ArtistIds" + index;
|
var paramName = "@ArtistIds" + index;
|
||||||
|
|
||||||
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
|
clauses.Add("((select CleanName from TypedBaseItems where guid=" + paramName + ") in (select CleanValue from ItemValues where ItemId=Guid and Type=0) AND (select CleanName from TypedBaseItems where guid=" + paramName + ") not in (select CleanValue from ItemValues where ItemId=Guid and Type=1))");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, artistId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, artistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3866,13 +3853,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var albumId in query.AlbumIds)
|
foreach (var albumId in query.AlbumIds)
|
||||||
{
|
{
|
||||||
var paramName = "@AlbumIds" + index;
|
var paramName = "@AlbumIds" + index;
|
||||||
|
|
||||||
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
clauses.Add("Album in (select Name from typedbaseitems where guid=" + paramName + ")");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, albumId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, albumId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3887,13 +3869,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var artistId in query.ExcludeArtistIds)
|
foreach (var artistId in query.ExcludeArtistIds)
|
||||||
{
|
{
|
||||||
var paramName = "@ExcludeArtistId" + index;
|
var paramName = "@ExcludeArtistId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
clauses.Add("(guid not in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type<=1))");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, artistId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, artistId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3908,13 +3885,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var genreId in query.GenreIds)
|
foreach (var genreId in query.GenreIds)
|
||||||
{
|
{
|
||||||
var paramName = "@GenreId" + index;
|
var paramName = "@GenreId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=2))");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, genreId);
|
||||||
{
|
|
||||||
statement.TryBind(paramName, genreId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3929,11 +3901,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var item in query.Genres)
|
foreach (var item in query.Genres)
|
||||||
{
|
{
|
||||||
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
|
clauses.Add("@Genre" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=2)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@Genre" + index, GetCleanValue(item));
|
||||||
{
|
|
||||||
statement.TryBind("@Genre" + index, GetCleanValue(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3948,11 +3916,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var item in tags)
|
foreach (var item in tags)
|
||||||
{
|
{
|
||||||
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
clauses.Add("@Tag" + index + " in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@Tag" + index, GetCleanValue(item));
|
||||||
{
|
|
||||||
statement.TryBind("@Tag" + index, GetCleanValue(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3967,11 +3931,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var item in excludeTags)
|
foreach (var item in excludeTags)
|
||||||
{
|
{
|
||||||
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
clauses.Add("@ExcludeTag" + index + " not in (select CleanValue from ItemValues where ItemId=Guid and Type=4)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@ExcludeTag" + index, GetCleanValue(item));
|
||||||
{
|
|
||||||
statement.TryBind("@ExcludeTag" + index, GetCleanValue(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3986,14 +3946,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var studioId in query.StudioIds)
|
foreach (var studioId in query.StudioIds)
|
||||||
{
|
{
|
||||||
var paramName = "@StudioId" + index;
|
var paramName = "@StudioId" + index;
|
||||||
|
|
||||||
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
clauses.Add("(guid in (select itemid from ItemValues where CleanValue = (select CleanName from TypedBaseItems where guid=" + paramName + ") and Type=3))");
|
||||||
|
statement?.TryBind(paramName, studioId);
|
||||||
if (statement is not null)
|
|
||||||
{
|
|
||||||
statement.TryBind(paramName, studioId);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4008,11 +3962,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
foreach (var item in query.OfficialRatings)
|
foreach (var item in query.OfficialRatings)
|
||||||
{
|
{
|
||||||
clauses.Add("OfficialRating=@OfficialRating" + index);
|
clauses.Add("OfficialRating=@OfficialRating" + index);
|
||||||
if (statement is not null)
|
statement?.TryBind("@OfficialRating" + index, item);
|
||||||
{
|
|
||||||
statement.TryBind("@OfficialRating" + index, item);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4020,34 +3970,96 @@ namespace Emby.Server.Implementations.Data
|
|||||||
whereClauses.Add(clause);
|
whereClauses.Add(clause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ratingClauseBuilder = new StringBuilder("(");
|
||||||
|
if (query.HasParentalRating ?? false)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append("InheritedParentalRatingValue not null");
|
||||||
if (query.MinParentalRating.HasValue)
|
if (query.MinParentalRating.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("InheritedParentalRatingValue>=@MinParentalRating");
|
ratingClauseBuilder.Append(" AND InheritedParentalRatingValue >= @MinParentalRating");
|
||||||
if (statement is not null)
|
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@MinParentalRating", query.MinParentalRating.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.MaxParentalRating.HasValue)
|
if (query.MaxParentalRating.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("InheritedParentalRatingValue<=@MaxParentalRating");
|
ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
|
||||||
|
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (query.BlockUnratedItems.Length > 0)
|
||||||
|
{
|
||||||
|
var paramName = "@UnratedType";
|
||||||
|
var index = 0;
|
||||||
|
string blockedUnratedItems = string.Join(',', query.BlockUnratedItems.Select(_ => paramName + index++));
|
||||||
|
ratingClauseBuilder.Append("(InheritedParentalRatingValue is null AND UnratedType not in (" + blockedUnratedItems + "))");
|
||||||
|
|
||||||
if (statement is not null)
|
if (statement is not null)
|
||||||
{
|
{
|
||||||
statement.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
|
for (var ind = 0; ind < query.BlockUnratedItems.Length; ind++)
|
||||||
|
{
|
||||||
|
statement.TryBind(paramName + ind, query.BlockUnratedItems[ind].ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.HasParentalRating.HasValue)
|
if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
|
||||||
{
|
{
|
||||||
if (query.HasParentalRating.Value)
|
ratingClauseBuilder.Append(" OR (");
|
||||||
{
|
|
||||||
whereClauses.Add("InheritedParentalRatingValue > 0");
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (query.MinParentalRating.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("InheritedParentalRatingValue = 0");
|
ratingClauseBuilder.Append("InheritedParentalRatingValue >= @MinParentalRating");
|
||||||
|
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.MaxParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
if (query.MinParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append(" AND ");
|
||||||
|
}
|
||||||
|
|
||||||
|
ratingClauseBuilder.Append("InheritedParentalRatingValue <= @MaxParentalRating");
|
||||||
|
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(query.MinParentalRating.HasValue || query.MaxParentalRating.HasValue))
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append(" OR InheritedParentalRatingValue not null");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (query.MinParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR (InheritedParentalRatingValue >= @MinParentalRating");
|
||||||
|
statement?.TryBind("@MinParentalRating", query.MinParentalRating.Value);
|
||||||
|
|
||||||
|
if (query.MaxParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append(" AND InheritedParentalRatingValue <= @MaxParentalRating");
|
||||||
|
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ratingClauseBuilder.Append(")");
|
||||||
|
}
|
||||||
|
else if (query.MaxParentalRating.HasValue)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append("InheritedParentalRatingValue is null OR InheritedParentalRatingValue <= @MaxParentalRating");
|
||||||
|
statement?.TryBind("@MaxParentalRating", query.MaxParentalRating.Value);
|
||||||
|
}
|
||||||
|
else if (!query.HasParentalRating ?? false)
|
||||||
|
{
|
||||||
|
ratingClauseBuilder.Append("InheritedParentalRatingValue is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var ratingClauseString = ratingClauseBuilder.ToString();
|
||||||
|
if (!string.Equals(ratingClauseString, "(", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
whereClauses.Add(ratingClauseString + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.HasOfficialRating.HasValue)
|
if (query.HasOfficialRating.HasValue)
|
||||||
@ -4089,37 +4101,25 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
|
if (!string.IsNullOrWhiteSpace(query.HasNoAudioTrackWithLanguage))
|
||||||
{
|
{
|
||||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
|
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Audio' and MediaStreams.Language=@HasNoAudioTrackWithLanguage limit 1) is null)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
|
||||||
{
|
|
||||||
statement.TryBind("@HasNoAudioTrackWithLanguage", query.HasNoAudioTrackWithLanguage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
|
if (!string.IsNullOrWhiteSpace(query.HasNoInternalSubtitleTrackWithLanguage))
|
||||||
{
|
{
|
||||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
|
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=0 and MediaStreams.Language=@HasNoInternalSubtitleTrackWithLanguage limit 1) is null)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
|
||||||
{
|
|
||||||
statement.TryBind("@HasNoInternalSubtitleTrackWithLanguage", query.HasNoInternalSubtitleTrackWithLanguage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
|
if (!string.IsNullOrWhiteSpace(query.HasNoExternalSubtitleTrackWithLanguage))
|
||||||
{
|
{
|
||||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
|
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.IsExternal=1 and MediaStreams.Language=@HasNoExternalSubtitleTrackWithLanguage limit 1) is null)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
|
||||||
{
|
|
||||||
statement.TryBind("@HasNoExternalSubtitleTrackWithLanguage", query.HasNoExternalSubtitleTrackWithLanguage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
|
if (!string.IsNullOrWhiteSpace(query.HasNoSubtitleTrackWithLanguage))
|
||||||
{
|
{
|
||||||
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
|
whereClauses.Add("((select language from MediaStreams where MediaStreams.ItemId=A.Guid and MediaStreams.StreamType='Subtitle' and MediaStreams.Language=@HasNoSubtitleTrackWithLanguage limit 1) is null)");
|
||||||
if (statement is not null)
|
statement?.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
|
||||||
{
|
|
||||||
statement.TryBind("@HasNoSubtitleTrackWithLanguage", query.HasNoSubtitleTrackWithLanguage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.HasSubtitles.HasValue)
|
if (query.HasSubtitles.HasValue)
|
||||||
@ -4169,15 +4169,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.Years.Length == 1)
|
if (query.Years.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("ProductionYear=@Years");
|
whereClauses.Add("ProductionYear=@Years");
|
||||||
if (statement is not null)
|
statement?.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
|
||||||
{
|
|
||||||
statement.TryBind("@Years", query.Years[0].ToString(CultureInfo.InvariantCulture));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4185,10 +4181,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (isVirtualItem.HasValue)
|
if (isVirtualItem.HasValue)
|
||||||
{
|
{
|
||||||
whereClauses.Add("IsVirtualItem=@IsVirtualItem");
|
whereClauses.Add("IsVirtualItem=@IsVirtualItem");
|
||||||
if (statement is not null)
|
statement?.TryBind("@IsVirtualItem", isVirtualItem.Value);
|
||||||
{
|
|
||||||
statement.TryBind("@IsVirtualItem", isVirtualItem.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.IsSpecialSeason.HasValue)
|
if (query.IsSpecialSeason.HasValue)
|
||||||
@ -4219,31 +4212,22 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (queryMediaTypes.Length == 1)
|
if (queryMediaTypes.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("MediaType=@MediaTypes");
|
whereClauses.Add("MediaType=@MediaTypes");
|
||||||
if (statement is not null)
|
statement?.TryBind("@MediaTypes", queryMediaTypes[0]);
|
||||||
{
|
|
||||||
statement.TryBind("@MediaTypes", queryMediaTypes[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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 + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ItemIds.Length > 0)
|
if (query.ItemIds.Length > 0)
|
||||||
{
|
{
|
||||||
var includeIds = new List<string>();
|
var includeIds = new List<string>();
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var id in query.ItemIds)
|
foreach (var id in query.ItemIds)
|
||||||
{
|
{
|
||||||
includeIds.Add("Guid = @IncludeId" + index);
|
includeIds.Add("Guid = @IncludeId" + index);
|
||||||
if (statement is not null)
|
statement?.TryBind("@IncludeId" + index, id);
|
||||||
{
|
|
||||||
statement.TryBind("@IncludeId" + index, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4253,16 +4237,11 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.ExcludeItemIds.Length > 0)
|
if (query.ExcludeItemIds.Length > 0)
|
||||||
{
|
{
|
||||||
var excludeIds = new List<string>();
|
var excludeIds = new List<string>();
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var id in query.ExcludeItemIds)
|
foreach (var id in query.ExcludeItemIds)
|
||||||
{
|
{
|
||||||
excludeIds.Add("Guid <> @ExcludeId" + index);
|
excludeIds.Add("Guid <> @ExcludeId" + index);
|
||||||
if (statement is not null)
|
statement?.TryBind("@ExcludeId" + index, id);
|
||||||
{
|
|
||||||
statement.TryBind("@ExcludeId" + index, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4283,11 +4262,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
var paramName = "@ExcludeProviderId" + index;
|
var paramName = "@ExcludeProviderId" + index;
|
||||||
excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
|
excludeIds.Add("(ProviderIds is null or ProviderIds not like " + paramName + ")");
|
||||||
if (statement is not null)
|
statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
||||||
{
|
|
||||||
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -4312,7 +4287,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
|
// TODO this seems to be an idea for a better schema where ProviderIds are their own table
|
||||||
// buut this is not implemented
|
// but this is not implemented
|
||||||
// hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
|
// hasProviderIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
|
||||||
|
|
||||||
// TODO this is a really BAD way to do it since the pair:
|
// TODO this is a really BAD way to do it since the pair:
|
||||||
@ -4326,11 +4301,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
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 is not null)
|
statement?.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
||||||
{
|
|
||||||
statement.TryBind(paramName, "%" + pair.Key + "=" + pair.Value + "%");
|
|
||||||
}
|
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -4407,11 +4378,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (query.AncestorIds.Length == 1)
|
if (query.AncestorIds.Length == 1)
|
||||||
{
|
{
|
||||||
whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
|
whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
|
||||||
|
statement?.TryBind("@AncestorId", query.AncestorIds[0]);
|
||||||
if (statement is not null)
|
|
||||||
{
|
|
||||||
statement.TryBind("@AncestorId", query.AncestorIds[0]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.AncestorIds.Length > 1)
|
if (query.AncestorIds.Length > 1)
|
||||||
@ -4424,39 +4391,13 @@ namespace Emby.Server.Implementations.Data
|
|||||||
{
|
{
|
||||||
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
|
var inClause = "select guid from TypedBaseItems where PresentationUniqueKey=@AncestorWithPresentationUniqueKey";
|
||||||
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
|
whereClauses.Add(string.Format(CultureInfo.InvariantCulture, "Guid in (select itemId from AncestorIds where AncestorId in ({0}))", inClause));
|
||||||
if (statement is not null)
|
statement?.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
|
||||||
{
|
|
||||||
statement.TryBind("@AncestorWithPresentationUniqueKey", query.AncestorWithPresentationUniqueKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
|
if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey))
|
||||||
{
|
{
|
||||||
whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
|
whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey");
|
||||||
|
statement?.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
|
||||||
if (statement is not null)
|
|
||||||
{
|
|
||||||
statement.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.BlockUnratedItems.Length == 1)
|
|
||||||
{
|
|
||||||
whereClauses.Add("(InheritedParentalRatingValue > 0 or UnratedType <> @UnratedType)");
|
|
||||||
if (statement is not null)
|
|
||||||
{
|
|
||||||
statement.TryBind("@UnratedType", query.BlockUnratedItems[0].ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.BlockUnratedItems.Length > 1)
|
|
||||||
{
|
|
||||||
var inClause = string.Join(',', query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'"));
|
|
||||||
whereClauses.Add(
|
|
||||||
string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
|
||||||
"(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))",
|
|
||||||
inClause));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ExcludeInheritedTags.Length > 0)
|
if (query.ExcludeInheritedTags.Length > 0)
|
||||||
@ -4605,6 +4546,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
|
|
||||||
return whereClauses;
|
return whereClauses;
|
||||||
}
|
}
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formats a where clause for the specified provider.
|
/// Formats a where clause for the specified provider.
|
||||||
|
@ -83,22 +83,23 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
|
public IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null)
|
||||||
{
|
{
|
||||||
var returnItems = new BaseItemDto[items.Count];
|
var accessibleItems = user is null ? items : items.Where(x => x.IsVisible(user)).ToList();
|
||||||
var programTuples = new List<(BaseItem, BaseItemDto)>();
|
var returnItems = new BaseItemDto[accessibleItems.Count];
|
||||||
var channelTuples = new List<(BaseItemDto, LiveTvChannel)>();
|
List<(BaseItem, BaseItemDto)> programTuples = null;
|
||||||
|
List<(BaseItemDto, LiveTvChannel)> channelTuples = null;
|
||||||
|
|
||||||
for (int index = 0; index < items.Count; index++)
|
for (int index = 0; index < accessibleItems.Count; index++)
|
||||||
{
|
{
|
||||||
var item = items[index];
|
var item = accessibleItems[index];
|
||||||
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
var dto = GetBaseItemDtoInternal(item, options, user, owner);
|
||||||
|
|
||||||
if (item is LiveTvChannel tvChannel)
|
if (item is LiveTvChannel tvChannel)
|
||||||
{
|
{
|
||||||
channelTuples.Add((dto, tvChannel));
|
(channelTuples ??= new()).Add((dto, tvChannel));
|
||||||
}
|
}
|
||||||
else if (item is LiveTvProgram)
|
else if (item is LiveTvProgram)
|
||||||
{
|
{
|
||||||
programTuples.Add((item, dto));
|
(programTuples ??= new()).Add((item, dto));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item is IItemByName byName)
|
if (item is IItemByName byName)
|
||||||
@ -121,12 +122,12 @@ namespace Emby.Server.Implementations.Dto
|
|||||||
returnItems[index] = dto;
|
returnItems[index] = dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (programTuples.Count > 0)
|
if (programTuples is not null)
|
||||||
{
|
{
|
||||||
LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
|
LivetvManager.AddInfoToProgramDto(programTuples, options.Fields, user).GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelTuples.Count > 0)
|
if (channelTuples is not null)
|
||||||
{
|
{
|
||||||
LivetvManager.AddChannelInfo(channelTuples, options, user);
|
LivetvManager.AddChannelInfo(channelTuples, options, user);
|
||||||
}
|
}
|
||||||
|
@ -356,8 +356,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
var children = item.IsFolder
|
var children = item.IsFolder
|
||||||
? ((Folder)item).GetRecursiveChildren(false).ToList()
|
? ((Folder)item).GetRecursiveChildren(false)
|
||||||
: new List<BaseItem>();
|
: Enumerable.Empty<BaseItem>();
|
||||||
|
|
||||||
foreach (var metadataPath in GetMetadataPaths(item, children))
|
foreach (var metadataPath in GetMetadataPaths(item, children))
|
||||||
{
|
{
|
||||||
@ -1253,7 +1253,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent is not null)
|
if (parent is not null)
|
||||||
{
|
{
|
||||||
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent });
|
SetTopParentIdsOrAncestors(query, new[] { parent });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1277,7 +1277,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent is not null)
|
if (parent is not null)
|
||||||
{
|
{
|
||||||
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent });
|
SetTopParentIdsOrAncestors(query, new[] { parent });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1435,7 +1435,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
var parent = GetItemById(query.ParentId);
|
var parent = GetItemById(query.ParentId);
|
||||||
if (parent is not null)
|
if (parent is not null)
|
||||||
{
|
{
|
||||||
SetTopParentIdsOrAncestors(query, new List<BaseItem> { parent });
|
SetTopParentIdsOrAncestors(query, new[] { parent });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1455,7 +1455,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
_itemRepository.GetItemList(query));
|
_itemRepository.GetItemList(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
|
private void SetTopParentIdsOrAncestors(InternalItemsQuery query, IReadOnlyCollection<BaseItem> parents)
|
||||||
{
|
{
|
||||||
if (parents.All(i => i is ICollectionFolder || i is UserView))
|
if (parents.All(i => i is ICollectionFolder || i is UserView))
|
||||||
{
|
{
|
||||||
@ -1602,7 +1602,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting intros");
|
_logger.LogError(ex, "Error getting intros");
|
||||||
|
|
||||||
return new List<IntroInfo>();
|
return Enumerable.Empty<IntroInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2876,7 +2876,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
|
private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var personsToSave = new List<BaseItem>();
|
List<BaseItem> personsToSave = null;
|
||||||
|
|
||||||
foreach (var person in people)
|
foreach (var person in people)
|
||||||
{
|
{
|
||||||
@ -2918,12 +2918,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (saveEntity)
|
if (saveEntity)
|
||||||
{
|
{
|
||||||
personsToSave.Add(personEntity);
|
(personsToSave ??= new()).Add(personEntity);
|
||||||
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
|
await RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (personsToSave.Count > 0)
|
if (personsToSave is not null)
|
||||||
{
|
{
|
||||||
CreateItems(personsToSave, null, CancellationToken.None);
|
CreateItems(personsToSave, null, CancellationToken.None);
|
||||||
}
|
}
|
||||||
@ -3085,22 +3085,19 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(path));
|
throw new ArgumentNullException(nameof(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
var removeList = new List<NameValuePair>();
|
List<NameValuePair> removeList = null;
|
||||||
|
|
||||||
foreach (var contentType in _configurationManager.Configuration.ContentTypes)
|
foreach (var contentType in _configurationManager.Configuration.ContentTypes)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(contentType.Name))
|
if (string.IsNullOrWhiteSpace(contentType.Name)
|
||||||
{
|
|| _fileSystem.AreEqual(path, contentType.Name)
|
||||||
removeList.Add(contentType);
|
|
||||||
}
|
|
||||||
else if (_fileSystem.AreEqual(path, contentType.Name)
|
|
||||||
|| _fileSystem.ContainsSubPath(path, contentType.Name))
|
|| _fileSystem.ContainsSubPath(path, contentType.Name))
|
||||||
{
|
{
|
||||||
removeList.Add(contentType);
|
(removeList ??= new()).Add(contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removeList.Count > 0)
|
if (removeList is not null)
|
||||||
{
|
{
|
||||||
_configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
|
_configurationManager.Configuration.ContentTypes = _configurationManager.Configuration.ContentTypes
|
||||||
.Except(removeList)
|
.Except(removeList)
|
||||||
|
@ -158,7 +158,6 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
|
private MultiItemResolverResult ResolveMultipleAudio(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, bool parseName)
|
||||||
{
|
{
|
||||||
var files = new List<FileSystemMetadata>();
|
var files = new List<FileSystemMetadata>();
|
||||||
var items = new List<BaseItem>();
|
|
||||||
var leftOver = new List<FileSystemMetadata>();
|
var leftOver = new List<FileSystemMetadata>();
|
||||||
|
|
||||||
// Loop through each child file/folder and see if we find a video
|
// Loop through each child file/folder and see if we find a video
|
||||||
@ -180,7 +179,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
|
|||||||
var result = new MultiItemResolverResult
|
var result = new MultiItemResolverResult
|
||||||
{
|
{
|
||||||
ExtraFiles = leftOver,
|
ExtraFiles = leftOver,
|
||||||
Items = items
|
Items = new List<BaseItem>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent);
|
var isInMixedFolder = resolverResult.Count > 1 || (parent is not null && parent.IsTopParent);
|
||||||
|
@ -286,7 +286,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (parents.Count == 0)
|
if (parents.Count == 0)
|
||||||
{
|
{
|
||||||
return new List<BaseItem>();
|
return Array.Empty<BaseItem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (includeItemTypes.Length == 0)
|
if (includeItemTypes.Length == 0)
|
||||||
|
@ -13,8 +13,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([0-9]+)-?([0-9]*)");
|
var match = Regex.Match(url, @"\/ch([0-9]+)-?([0-9]*)");
|
||||||
var match = regExp.Match(url);
|
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
_channel = match.Groups[1].Value;
|
_channel = match.Groups[1].Value;
|
||||||
|
@ -308,8 +308,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
|||||||
{
|
{
|
||||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
|
var matches = Regex.Matches(line, @"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
|
||||||
var matches = reg.Matches(line);
|
|
||||||
|
|
||||||
remaining = line;
|
remaining = line;
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
|
"ItemRemovedWithName": "{0} ha sido eliminado de la biblioteca",
|
||||||
"LabelIpAddressValue": "Dirección IP: {0}",
|
"LabelIpAddressValue": "Dirección IP: {0}",
|
||||||
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
|
"LabelRunningTimeValue": "Tiempo de funcionamiento: {0}",
|
||||||
"Latest": "Últimos",
|
"Latest": "Último contenido en",
|
||||||
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
|
"MessageApplicationUpdated": "Se ha actualizado el servidor Jellyfin",
|
||||||
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
|
"MessageApplicationUpdatedTo": "Se ha actualizado el servidor Jellyfin a la versión {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",
|
"MessageNamedServerConfigurationUpdatedWithValue": "La sección {0} de configuración del servidor ha sido actualizada",
|
||||||
|
@ -67,5 +67,11 @@
|
|||||||
"Plugin": "प्लग-इन",
|
"Plugin": "प्लग-इन",
|
||||||
"Playlists": "प्लेलिस्ट",
|
"Playlists": "प्लेलिस्ट",
|
||||||
"Photos": "तस्वीरें",
|
"Photos": "तस्वीरें",
|
||||||
"External": "बाहरी"
|
"External": "बाहरी",
|
||||||
|
"PluginUpdatedWithName": "{0} अपडेट हुए",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} शुरू हुए",
|
||||||
|
"Songs": "गाने",
|
||||||
|
"UserStartedPlayingItemWithValues": "{0} {2} पर {1} खेल रहे हैं",
|
||||||
|
"UserStoppedPlayingItemWithValues": "{0} ने {2} पर {1} खेलना खत्म किया",
|
||||||
|
"StartupEmbyServerIsLoading": "जेलीफ़िन सर्वर लोड हो रहा है। कृपया शीघ्र ही पुन: प्रयास करें।"
|
||||||
}
|
}
|
||||||
|
@ -95,13 +95,13 @@
|
|||||||
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.",
|
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar ontbrekende ondertiteling gebaseerd op metadataconfiguratie.",
|
||||||
"TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden",
|
"TaskDownloadMissingSubtitles": "Ontbrekende ondertiteling downloaden",
|
||||||
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
||||||
"TaskRefreshChannels": "Vernieuw kanalen",
|
"TaskRefreshChannels": "Kanalen vernieuwen",
|
||||||
"TaskCleanTranscodeDescription": "Verwijdert transcode bestanden ouder dan 1 dag.",
|
"TaskCleanTranscodeDescription": "Verwijdert transcode bestanden ouder dan 1 dag.",
|
||||||
"TaskCleanLogs": "Logboekmap opschonen",
|
"TaskCleanLogs": "Logboekmap opschonen",
|
||||||
"TaskCleanTranscode": "Transcoderingsmap opschonen",
|
"TaskCleanTranscode": "Transcoderingsmap opschonen",
|
||||||
"TaskUpdatePluginsDescription": "Downloadt en installeert updates van plug-ins waarvoor automatisch bijwerken is ingeschakeld.",
|
"TaskUpdatePluginsDescription": "Downloadt en installeert updates van plug-ins waarvoor automatisch bijwerken is ingeschakeld.",
|
||||||
"TaskUpdatePlugins": "Plug-ins bijwerken",
|
"TaskUpdatePlugins": "Plug-ins bijwerken",
|
||||||
"TaskRefreshPeopleDescription": "Update metadata voor acteurs en regisseurs in de media bibliotheek.",
|
"TaskRefreshPeopleDescription": "Updatet metadata voor acteurs en regisseurs in je mediabibliotheek.",
|
||||||
"TaskRefreshPeople": "Personen vernieuwen",
|
"TaskRefreshPeople": "Personen vernieuwen",
|
||||||
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
||||||
"TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.",
|
"TaskRefreshLibraryDescription": "Scant de mediabibliotheek op nieuwe bestanden en vernieuwt de metadata.",
|
||||||
|
@ -86,7 +86,7 @@
|
|||||||
"Shows": "Шоу",
|
"Shows": "Шоу",
|
||||||
"ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити",
|
"ServerNameNeedsToBeRestarted": "{0} потрібно перезапустити",
|
||||||
"ScheduledTaskStartedWithName": "{0} розпочато",
|
"ScheduledTaskStartedWithName": "{0} розпочато",
|
||||||
"ScheduledTaskFailedWithName": "Помилка {0}",
|
"ScheduledTaskFailedWithName": "{0} незавершено, збій",
|
||||||
"ProviderValue": "Постачальник: {0}",
|
"ProviderValue": "Постачальник: {0}",
|
||||||
"PluginUpdatedWithName": "{0} оновлено",
|
"PluginUpdatedWithName": "{0} оновлено",
|
||||||
"PluginUninstalledWithName": "{0} видалено",
|
"PluginUninstalledWithName": "{0} видалено",
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -25,7 +26,7 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
|
private const string CulturesPath = "Emby.Server.Implementations.Localization.iso6392.txt";
|
||||||
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
|
private const string CountriesPath = "Emby.Server.Implementations.Localization.countries.json";
|
||||||
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
private static readonly Assembly _assembly = typeof(LocalizationManager).Assembly;
|
||||||
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated" };
|
private static readonly string[] _unratedValues = { "n/a", "unrated", "not rated", "nr" };
|
||||||
|
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
private readonly ILogger<LocalizationManager> _logger;
|
private readonly ILogger<LocalizationManager> _logger;
|
||||||
@ -86,12 +87,10 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
var name = parts[0];
|
var name = parts[0];
|
||||||
dict.Add(name, new ParentalRating(name, value));
|
dict.Add(name, new ParentalRating(name, value));
|
||||||
}
|
}
|
||||||
#if DEBUG
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
|
_logger.LogWarning("Malformed line in ratings file for country {CountryCode}", countryCode);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_allParentalRatings[countryCode] = dict;
|
_allParentalRatings[countryCode] = dict;
|
||||||
@ -184,7 +183,56 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ParentalRating> GetParentalRatings()
|
public IEnumerable<ParentalRating> GetParentalRatings()
|
||||||
=> GetParentalRatingsDictionary().Values;
|
{
|
||||||
|
var ratings = GetParentalRatingsDictionary().Values.ToList();
|
||||||
|
|
||||||
|
// Add common ratings to ensure them being available for selection.
|
||||||
|
// Based on the US rating system due to it being the main source of rating in the metadata providers
|
||||||
|
// Minimum rating possible
|
||||||
|
if (!ratings.Any(x => x.Value == 0))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("Approved", 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches PG (this has different age restrictions depending on country)
|
||||||
|
if (!ratings.Any(x => x.Value == 10))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("10", 10));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches PG-13
|
||||||
|
if (!ratings.Any(x => x.Value == 13))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("13", 13));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches TV-14
|
||||||
|
if (!ratings.Any(x => x.Value == 14))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("14", 14));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Catchall if max rating of country is less than 21
|
||||||
|
// Using 21 instead of 18 to be sure to allow access to all rated content except adult and banned
|
||||||
|
if (!ratings.Any(x => x.Value >= 21))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("21", 21));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lot of countries don't excplicitly have a seperate rating for adult content
|
||||||
|
if (!ratings.Any(x => x.Value == 1000))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("XXX", 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
// A lot of countries don't excplicitly have a seperate rating for banned content
|
||||||
|
if (!ratings.Any(x => x.Value == 1001))
|
||||||
|
{
|
||||||
|
ratings.Add(new ParentalRating("Banned", 1001));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ratings.OrderBy(r => r.Value);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parental ratings dictionary.
|
/// Gets the parental ratings dictionary.
|
||||||
@ -194,6 +242,7 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
{
|
{
|
||||||
var countryCode = _configurationManager.Configuration.MetadataCountryCode;
|
var countryCode = _configurationManager.Configuration.MetadataCountryCode;
|
||||||
|
|
||||||
|
// Fall back to US ratings if no country code is specified or country code does not exist.
|
||||||
if (string.IsNullOrEmpty(countryCode))
|
if (string.IsNullOrEmpty(countryCode))
|
||||||
{
|
{
|
||||||
countryCode = "us";
|
countryCode = "us";
|
||||||
@ -205,15 +254,15 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the ratings.
|
/// Gets the ratings for a country.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="countryCode">The country code.</param>
|
/// <param name="countryCode">The country code.</param>
|
||||||
/// <returns>The ratings.</returns>
|
/// <returns>The ratings.</returns>
|
||||||
private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
|
private Dictionary<string, ParentalRating>? GetRatings(string countryCode)
|
||||||
{
|
{
|
||||||
_allParentalRatings.TryGetValue(countryCode, out var value);
|
_allParentalRatings.TryGetValue(countryCode, out var countryValue);
|
||||||
|
|
||||||
return value;
|
return countryValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -221,12 +270,14 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
{
|
{
|
||||||
ArgumentException.ThrowIfNullOrEmpty(rating);
|
ArgumentException.ThrowIfNullOrEmpty(rating);
|
||||||
|
|
||||||
|
// Handle unrated content
|
||||||
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
|
if (_unratedValues.Contains(rating.AsSpan(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fairly common for some users to have "Rated R" in their rating field
|
// Fairly common for some users to have "Rated R" in their rating field
|
||||||
|
rating = rating.Replace("Rated :", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
|
rating = rating.Replace("Rated ", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var ratingsDictionary = GetParentalRatingsDictionary();
|
var ratingsDictionary = GetParentalRatingsDictionary();
|
||||||
@ -246,18 +297,17 @@ namespace Emby.Server.Implementations.Localization
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Try splitting by : to handle "Germany: FSK 18"
|
// Try splitting by : to handle "Germany: FSK 18"
|
||||||
var index = rating.IndexOf(':', StringComparison.Ordinal);
|
if (rating.Contains(':', StringComparison.OrdinalIgnoreCase))
|
||||||
if (index != -1)
|
|
||||||
{
|
{
|
||||||
var trimmedRating = rating.AsSpan(index).TrimStart(':').Trim();
|
return GetRatingLevel(rating.AsSpan().RightPart(':').ToString());
|
||||||
|
}
|
||||||
if (!trimmedRating.IsEmpty)
|
|
||||||
{
|
// Remove prefix country code to handle "DE-18"
|
||||||
return GetRatingLevel(trimmedRating.ToString());
|
if (rating.Contains('-', StringComparison.OrdinalIgnoreCase))
|
||||||
}
|
{
|
||||||
|
return GetRatingLevel(rating.AsSpan().RightPart('-').ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Further improve by normalizing out all spaces and dashes
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
E,0
|
||||||
|
EC,0
|
||||||
|
T,7
|
||||||
|
M,18
|
||||||
|
AO,18
|
||||||
|
UR,18
|
||||||
|
RP,18
|
||||||
|
X,1000
|
||||||
|
XX,1000
|
||||||
|
XXX,1000
|
||||||
|
XXXX,1000
|
|
@ -1,7 +1,13 @@
|
|||||||
AU-G,1
|
Exempt,0
|
||||||
AU-PG,5
|
G,0
|
||||||
AU-M,6
|
7+,7
|
||||||
AU-MA15+,7
|
M,15
|
||||||
AU-R18+,9
|
MA,15
|
||||||
AU-X18+,10
|
MA15+,15
|
||||||
AU-RC,11
|
PG,16
|
||||||
|
16+,16
|
||||||
|
R,18
|
||||||
|
R18+,18
|
||||||
|
X18+,18
|
||||||
|
18+,18
|
||||||
|
X,1000
|
||||||
|
|
@ -1,6 +1,11 @@
|
|||||||
BE-AL,1
|
AL,0
|
||||||
BE-MG6,2
|
KT,0
|
||||||
BE-6,3
|
TOUS,0
|
||||||
BE-9,5
|
MG6,6
|
||||||
BE-12,6
|
6,6
|
||||||
BE-16,8
|
9,9
|
||||||
|
KNT,12
|
||||||
|
12,12
|
||||||
|
14,14
|
||||||
|
16,16
|
||||||
|
18,18
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
BR-L,1
|
Livre,0
|
||||||
BR-10,5
|
L,0
|
||||||
BR-12,7
|
ER,9
|
||||||
BR-14,8
|
10,10
|
||||||
BR-16,8
|
12,12
|
||||||
BR-18,9
|
14,14
|
||||||
|
16,16
|
||||||
|
18,18
|
||||||
|
|
@ -1,6 +1,20 @@
|
|||||||
CA-G,1
|
E,0
|
||||||
CA-PG,5
|
G,0
|
||||||
CA-14A,7
|
TV-Y,0
|
||||||
CA-A,8
|
TV-G,0
|
||||||
CA-18A,9
|
TV-Y7,7
|
||||||
CA-R,10
|
TV-Y7-FV,7
|
||||||
|
PG,9
|
||||||
|
TV-PG,9
|
||||||
|
PG-13,13
|
||||||
|
13+,13
|
||||||
|
TV-14,14
|
||||||
|
14A,14
|
||||||
|
16+,16
|
||||||
|
NC-17,17
|
||||||
|
R,18
|
||||||
|
TV-MA,18
|
||||||
|
18A,18
|
||||||
|
18+,18
|
||||||
|
A,1000
|
||||||
|
Prohibited,1001
|
||||||
|
|
@ -1,8 +1,7 @@
|
|||||||
CO-T,1
|
T,0
|
||||||
CO-7,5
|
7,7
|
||||||
CO-12,7
|
12,12
|
||||||
CO-15,8
|
15,15
|
||||||
CO-18,10
|
18,18
|
||||||
CO-X,100
|
X,1000
|
||||||
CO-BANNED,15
|
Prohibited,1001
|
||||||
CO-E,15
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
|||||||
DE-0,1
|
Educational,0
|
||||||
FSK-0,1
|
Infoprogramm,0
|
||||||
DE-6,5
|
FSK-0,0
|
||||||
FSK-6,5
|
0,0
|
||||||
DE-12,7
|
FSK-6,6
|
||||||
FSK-12,7
|
6,6
|
||||||
DE-16,8
|
FSK-12,12
|
||||||
FSK-16,8
|
12,12
|
||||||
DE-18,9
|
FSK-16,16
|
||||||
FSK-18,9
|
16,16
|
||||||
|
FSK-18,18
|
||||||
|
18,18
|
||||||
|
|
@ -1,4 +1,7 @@
|
|||||||
DA-A,1
|
F,0
|
||||||
DA-7,5
|
A,0
|
||||||
DA-11,6
|
7,7
|
||||||
DA-15,8
|
11,11
|
||||||
|
12,12
|
||||||
|
15,15
|
||||||
|
16,16
|
||||||
|
|
@ -1,6 +1,24 @@
|
|||||||
ES-A,1
|
A,0
|
||||||
ES-APTA,1
|
A/fig,0
|
||||||
ES-7,3
|
A/i,0
|
||||||
ES-12,6
|
A/fig/i,0
|
||||||
ES-16,8
|
APTA,0
|
||||||
ES-18,11
|
TP,0
|
||||||
|
0+,0
|
||||||
|
6+,6
|
||||||
|
7/fig,7
|
||||||
|
7/i,7
|
||||||
|
7/i/fig,7
|
||||||
|
7,7
|
||||||
|
9+,9
|
||||||
|
10,10
|
||||||
|
12,12
|
||||||
|
12/fig,12
|
||||||
|
13,13
|
||||||
|
14,14
|
||||||
|
16,16
|
||||||
|
16/fig,16
|
||||||
|
18,18
|
||||||
|
18/fig,18
|
||||||
|
X,1000
|
||||||
|
Banned,1001
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
FI-S,1
|
S,0
|
||||||
FI-T,1
|
T,0
|
||||||
FI-7,4
|
K7,7
|
||||||
FI-12,5
|
7,7
|
||||||
FI-16,8
|
K12,12
|
||||||
FI-18,9
|
12,12
|
||||||
FI-K7,4
|
K16,16
|
||||||
FI-K12,5
|
16,16
|
||||||
FI-K16,8
|
K18,18
|
||||||
FI-K18,9
|
18,18
|
||||||
|
|
@ -1,5 +1,12 @@
|
|||||||
FR-U,1
|
Public Averti,0
|
||||||
FR-10,5
|
Tous Publics,0
|
||||||
FR-12,7
|
U,0
|
||||||
FR-16,9
|
0+,0
|
||||||
FR-18,10
|
6+,6
|
||||||
|
9+,9
|
||||||
|
10,10
|
||||||
|
12,12
|
||||||
|
14+,14
|
||||||
|
16,16
|
||||||
|
18,18
|
||||||
|
X,1000
|
||||||
|
|
@ -1,7 +1,22 @@
|
|||||||
GB-U,1
|
All,0
|
||||||
GB-PG,5
|
E,0
|
||||||
GB-12,6
|
G,0
|
||||||
GB-12A,7
|
U,0
|
||||||
GB-15,8
|
0+,0
|
||||||
GB-18,9
|
6+,6
|
||||||
GB-R18,15
|
7+,7
|
||||||
|
PG,8
|
||||||
|
9+,9
|
||||||
|
12,12
|
||||||
|
12+,12
|
||||||
|
12A,12
|
||||||
|
Teen,13
|
||||||
|
13+,13
|
||||||
|
14+,14
|
||||||
|
15,15
|
||||||
|
16,16
|
||||||
|
Caution,18
|
||||||
|
18,18
|
||||||
|
Mature,1000
|
||||||
|
Adult,1000
|
||||||
|
R18,1000
|
||||||
|
|
@ -1,6 +1,9 @@
|
|||||||
IE-G,1
|
G,4
|
||||||
IE-PG,5
|
PG,12
|
||||||
IE-12A,7
|
12,12
|
||||||
IE-15A,8
|
12A,12
|
||||||
IE-16,9
|
12PG,12
|
||||||
IE-18,10
|
15,15
|
||||||
|
15A,15
|
||||||
|
16,16
|
||||||
|
18,18
|
||||||
|
|
@ -1,4 +1,11 @@
|
|||||||
JP-G,1
|
A,0
|
||||||
JP-PG12,7
|
G,0
|
||||||
JP-15+,8
|
B,12
|
||||||
JP-18+,10
|
PG12,12
|
||||||
|
C,15
|
||||||
|
15+,15
|
||||||
|
R15+,15
|
||||||
|
16+,16
|
||||||
|
D,17
|
||||||
|
Z,18
|
||||||
|
18+,18
|
||||||
|
|
@ -1,7 +1,6 @@
|
|||||||
KZ-6-,0
|
K,0
|
||||||
KZ-6+,6
|
БА,12
|
||||||
KZ-12+,12
|
Б14,14
|
||||||
KZ-14+,14
|
E16,16
|
||||||
KZ-16+,16
|
E18,18
|
||||||
KZ-18+,18
|
HA,18
|
||||||
KZ-21+,21
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
MX-AA,1
|
A,0
|
||||||
MX-A,5
|
AA,0
|
||||||
MX-B,7
|
B,12
|
||||||
MX-B-15,8
|
B-15,15
|
||||||
MX-C,9
|
C,18
|
||||||
MX-D,10
|
D,1000
|
||||||
|
|
@ -1,6 +1,8 @@
|
|||||||
NL-AL,1
|
AL,0
|
||||||
NL-MG6,2
|
MG6,6
|
||||||
NL-6,3
|
6,6
|
||||||
NL-9,5
|
9,9
|
||||||
NL-12,6
|
12,12
|
||||||
NL-16,8
|
14,14
|
||||||
|
16,16
|
||||||
|
18,18
|
||||||
|
|
@ -1,6 +1,9 @@
|
|||||||
NO-A,1
|
A,0
|
||||||
NO-6,3
|
6,6
|
||||||
NO-9,4
|
7,7
|
||||||
NO-12,5
|
9,9
|
||||||
NO-15,8
|
11,11
|
||||||
NO-18,9
|
12,12
|
||||||
|
15,15
|
||||||
|
18,18
|
||||||
|
Not approved,1001
|
||||||
|
|
@ -1,11 +1,15 @@
|
|||||||
NZ-G,1
|
Exempt,0
|
||||||
NZ-PG,5
|
G,0
|
||||||
NZ-M,6
|
GY,13
|
||||||
NZ-R13,7
|
PG,13
|
||||||
NZ-RP13,7
|
R13,13
|
||||||
NZ-R15,8
|
RP13,13
|
||||||
NZ-RP16,9
|
R15,15
|
||||||
NZ-R16,9
|
M,16
|
||||||
NZ-R18,10
|
R16,16
|
||||||
NZ-R,10
|
RP16,16
|
||||||
NZ-MA,10
|
GA,18
|
||||||
|
R18,18
|
||||||
|
MA,1000
|
||||||
|
R,1001
|
||||||
|
Objectionable,1001
|
||||||
|
|
@ -1 +1,6 @@
|
|||||||
RO-AG,1
|
AG,0
|
||||||
|
AP-12,12
|
||||||
|
N-15,15
|
||||||
|
IM-18,18
|
||||||
|
IM-18-XXX,1000
|
||||||
|
IC,1001
|
||||||
|
|
@ -1,5 +1,6 @@
|
|||||||
RU-0+,1
|
0+,0
|
||||||
RU-6+,3
|
6+,6
|
||||||
RU-12+,7
|
12+,12
|
||||||
RU-16+,9
|
16+,16
|
||||||
RU-18+,10
|
18+,18
|
||||||
|
Refused classification,1001
|
||||||
|
|
@ -1,5 +1,10 @@
|
|||||||
SE-Btl,1
|
Alla,0
|
||||||
SE-Barntillåten,1
|
Barntillåten,0
|
||||||
SE-7,3
|
Btl,0
|
||||||
SE-11,5
|
0+,0
|
||||||
SE-15,8
|
7,7
|
||||||
|
9+,9
|
||||||
|
10+,10
|
||||||
|
11,11
|
||||||
|
14,14
|
||||||
|
15,15
|
||||||
|
|
@ -1,7 +1,22 @@
|
|||||||
UK-U,1
|
All,0
|
||||||
UK-PG,5
|
E,0
|
||||||
UK-12,7
|
G,0
|
||||||
UK-12A,7
|
U,0
|
||||||
UK-15,9
|
0+,0
|
||||||
UK-18,10
|
6+,6
|
||||||
UK-R18,15
|
7+,7
|
||||||
|
PG,8
|
||||||
|
9+,9
|
||||||
|
12,12
|
||||||
|
12+,12
|
||||||
|
12A,12
|
||||||
|
Teen,13
|
||||||
|
13+,13
|
||||||
|
14+,14
|
||||||
|
15,15
|
||||||
|
16,16
|
||||||
|
Caution,18
|
||||||
|
18,18
|
||||||
|
Mature,1000
|
||||||
|
Adult,1000
|
||||||
|
R18,1000
|
||||||
|
|
@ -1,23 +1,50 @@
|
|||||||
TV-Y,1
|
Approved,0
|
||||||
APPROVED,1
|
G,0
|
||||||
G,1
|
TV-G,0
|
||||||
E,1
|
TV-Y,0
|
||||||
EC,1
|
TV-Y7,7
|
||||||
TV-G,1
|
TV-Y7-FV,7
|
||||||
TV-Y7,3
|
PG,10
|
||||||
TV-Y7-FV,4
|
PG-13,13
|
||||||
PG,5
|
TV-PG,13
|
||||||
TV-PG,5
|
TV-PG-D,13
|
||||||
PG-13,7
|
TV-PG-L,13
|
||||||
T,7
|
TV-PG-S,13
|
||||||
TV-14,8
|
TV-PG-V,13
|
||||||
R,9
|
TV-PG-DL,13
|
||||||
M,9
|
TV-PG-DS,13
|
||||||
TV-MA,9
|
TV-PG-DV,13
|
||||||
NC-17,10
|
TV-PG-LS,13
|
||||||
AO,15
|
TV-PG-LV,13
|
||||||
RP,15
|
TV-PG-SV,13
|
||||||
UR,15
|
TV-PG-DLS,13
|
||||||
NR,15
|
TV-PG-DLV,13
|
||||||
X,15
|
TV-PG-DSV,13
|
||||||
XXX,100
|
TV-PG-LSV,13
|
||||||
|
TV-PG-DLSV,13
|
||||||
|
TV-14,14
|
||||||
|
TV-14-D,14
|
||||||
|
TV-14-L,14
|
||||||
|
TV-14-S,14
|
||||||
|
TV-14-V,14
|
||||||
|
TV-14-DL,14
|
||||||
|
TV-14-DS,14
|
||||||
|
TV-14-DV,14
|
||||||
|
TV-14-LS,14
|
||||||
|
TV-14-LV,14
|
||||||
|
TV-14-SV,14
|
||||||
|
TV-14-DLS,14
|
||||||
|
TV-14-DLV,14
|
||||||
|
TV-14-DSV,14
|
||||||
|
TV-14-LSV,14
|
||||||
|
TV-14-DLSV,14
|
||||||
|
NC-17,17
|
||||||
|
R,17
|
||||||
|
TV-MA,17
|
||||||
|
TV-MA-L,17
|
||||||
|
TV-MA-S,17
|
||||||
|
TV-MA-V,17
|
||||||
|
TV-MA-LS,17
|
||||||
|
TV-MA-LV,17
|
||||||
|
TV-MA-SV,17
|
||||||
|
TV-MA-LSV,17
|
||||||
|
|
@ -93,11 +93,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
|
public ScheduledTaskWorker(IScheduledTask scheduledTask, IApplicationPaths applicationPaths, ITaskManager taskManager, ILogger logger)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(scheduledTask);
|
ArgumentNullException.ThrowIfNull(scheduledTask);
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(applicationPaths);
|
ArgumentNullException.ThrowIfNull(applicationPaths);
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(taskManager);
|
ArgumentNullException.ThrowIfNull(taskManager);
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(logger);
|
ArgumentNullException.ThrowIfNull(logger);
|
||||||
|
|
||||||
ScheduledTask = scheduledTask;
|
ScheduledTask = scheduledTask;
|
||||||
@ -332,7 +329,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("{0} fired for task: {1}", trigger.GetType().Name, Name);
|
_logger.LogDebug("{0} fired for task: {1}", trigger.GetType().Name, Name);
|
||||||
|
|
||||||
trigger.Stop();
|
trigger.Stop();
|
||||||
|
|
||||||
@ -378,7 +375,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
|
|
||||||
CurrentCancellationTokenSource = new CancellationTokenSource();
|
CurrentCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
_logger.LogInformation("Executing {0}", Name);
|
_logger.LogDebug("Executing {0}", Name);
|
||||||
|
|
||||||
((TaskManager)_taskManager).OnTaskExecuting(this);
|
((TaskManager)_taskManager).OnTaskExecuting(this);
|
||||||
|
|
||||||
@ -406,7 +403,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error");
|
_logger.LogError(ex, "Error executing Scheduled Task");
|
||||||
|
|
||||||
failureException = ex;
|
failureException = ex;
|
||||||
|
|
||||||
|
@ -132,7 +132,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
var type = scheduledTask.ScheduledTask.GetType();
|
var type = scheduledTask.ScheduledTask.GetType();
|
||||||
|
|
||||||
_logger.LogInformation("Queuing task {0}", type.Name);
|
_logger.LogDebug("Queuing task {0}", type.Name);
|
||||||
|
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
{
|
{
|
||||||
@ -172,7 +172,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
{
|
{
|
||||||
var type = task.ScheduledTask.GetType();
|
var type = task.ScheduledTask.GetType();
|
||||||
|
|
||||||
_logger.LogInformation("Queuing task {0}", type.Name);
|
_logger.LogDebug("Queuing task {0}", type.Name);
|
||||||
|
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
{
|
{
|
||||||
@ -254,9 +254,6 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void ExecuteQueuedTasks()
|
private void ExecuteQueuedTasks()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("ExecuteQueuedTasks");
|
|
||||||
|
|
||||||
// Execute queued tasks
|
|
||||||
lock (_taskQueue)
|
lock (_taskQueue)
|
||||||
{
|
{
|
||||||
var list = new List<Tuple<Type, TaskOptions>>();
|
var list = new List<Tuple<Type, TaskOptions>>();
|
||||||
|
@ -46,6 +46,13 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isApiKey)
|
||||||
|
{
|
||||||
|
// Api keys are unrestricted.
|
||||||
|
context.Succeed(requirement);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
|
var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
|
||||||
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIP());
|
&& _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIP());
|
||||||
var user = _userManager.GetUserById(userId);
|
var user = _userManager.GetUserById(userId);
|
||||||
@ -62,7 +69,7 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Admins can do everything
|
// Admins can do everything
|
||||||
if (isApiKey || context.User.IsInRole(UserRoles.Administrator))
|
if (context.User.IsInRole(UserRoles.Administrator))
|
||||||
{
|
{
|
||||||
context.Succeed(requirement);
|
context.Succeed(requirement);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
@ -172,12 +172,9 @@ public class GenresController : BaseJellyfinApiController
|
|||||||
|
|
||||||
item ??= new Genre();
|
item ??= new Genre();
|
||||||
|
|
||||||
if (userId.Value.Equals(default))
|
var user = userId.Value.Equals(default)
|
||||||
{
|
? null
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions);
|
: _userManager.GetUserById(userId.Value);
|
||||||
}
|
|
||||||
|
|
||||||
var user = _userManager.GetUserById(userId.Value);
|
|
||||||
|
|
||||||
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
}).ToList());
|
}).ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateItem(request, item);
|
await UpdateItem(request, item).ConfigureAwait(false);
|
||||||
|
|
||||||
item.OnMetadataChanged();
|
item.OnMetadataChanged();
|
||||||
|
|
||||||
@ -147,7 +147,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
|
|
||||||
var info = new MetadataEditorInfo
|
var info = new MetadataEditorInfo
|
||||||
{
|
{
|
||||||
ParentalRatingOptions = _localizationManager.GetParentalRatings().ToArray(),
|
ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
|
||||||
ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
|
ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToArray(),
|
||||||
Countries = _localizationManager.GetCountries().ToArray(),
|
Countries = _localizationManager.GetCountries().ToArray(),
|
||||||
Cultures = _localizationManager.GetCultures().ToArray()
|
Cultures = _localizationManager.GetCultures().ToArray()
|
||||||
@ -224,7 +224,7 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateItem(BaseItemDto request, BaseItem item)
|
private async Task UpdateItem(BaseItemDto request, BaseItem item)
|
||||||
{
|
{
|
||||||
item.Name = request.Name;
|
item.Name = request.Name;
|
||||||
item.ForcedSortName = request.ForcedSortName;
|
item.ForcedSortName = request.ForcedSortName;
|
||||||
@ -266,9 +266,50 @@ public class ItemUpdateController : BaseJellyfinApiController
|
|||||||
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null;
|
item.EndDate = request.EndDate.HasValue ? NormalizeDateTime(request.EndDate.Value) : null;
|
||||||
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null;
|
item.PremiereDate = request.PremiereDate.HasValue ? NormalizeDateTime(request.PremiereDate.Value) : null;
|
||||||
item.ProductionYear = request.ProductionYear;
|
item.ProductionYear = request.ProductionYear;
|
||||||
item.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
|
|
||||||
|
request.OfficialRating = string.IsNullOrWhiteSpace(request.OfficialRating) ? null : request.OfficialRating;
|
||||||
|
item.OfficialRating = request.OfficialRating;
|
||||||
item.CustomRating = request.CustomRating;
|
item.CustomRating = request.CustomRating;
|
||||||
|
|
||||||
|
if (item is Series rseries)
|
||||||
|
{
|
||||||
|
foreach (Season season in rseries.Children)
|
||||||
|
{
|
||||||
|
season.OfficialRating = request.OfficialRating;
|
||||||
|
season.CustomRating = request.CustomRating;
|
||||||
|
season.OnMetadataChanged();
|
||||||
|
await season.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
|
||||||
|
foreach (Episode ep in season.Children)
|
||||||
|
{
|
||||||
|
ep.OfficialRating = request.OfficialRating;
|
||||||
|
ep.CustomRating = request.CustomRating;
|
||||||
|
ep.OnMetadataChanged();
|
||||||
|
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item is Season season)
|
||||||
|
{
|
||||||
|
foreach (Episode ep in season.Children)
|
||||||
|
{
|
||||||
|
ep.OfficialRating = request.OfficialRating;
|
||||||
|
ep.CustomRating = request.CustomRating;
|
||||||
|
ep.OnMetadataChanged();
|
||||||
|
await ep.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (item is MusicAlbum album)
|
||||||
|
{
|
||||||
|
foreach (BaseItem track in album.Children)
|
||||||
|
{
|
||||||
|
track.OfficialRating = request.OfficialRating;
|
||||||
|
track.CustomRating = request.CustomRating;
|
||||||
|
track.OnMetadataChanged();
|
||||||
|
await track.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request.ProductionLocations is not null)
|
if (request.ProductionLocations is not null)
|
||||||
{
|
{
|
||||||
item.ProductionLocations = request.ProductionLocations;
|
item.ProductionLocations = request.ProductionLocations;
|
||||||
|
@ -411,6 +411,13 @@ public class ItemsController : BaseJellyfinApiController
|
|||||||
query.SeriesStatuses = seriesStatus;
|
query.SeriesStatuses = seriesStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude Blocked Unrated Items
|
||||||
|
var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
|
||||||
|
if (blockedUnratedItems is not null)
|
||||||
|
{
|
||||||
|
query.BlockUnratedItems = blockedUnratedItems;
|
||||||
|
}
|
||||||
|
|
||||||
// ExcludeLocationTypes
|
// ExcludeLocationTypes
|
||||||
if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
|
if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Extensions;
|
using Jellyfin.Api.Extensions;
|
||||||
using Jellyfin.Api.ModelBinders;
|
using Jellyfin.Api.ModelBinders;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
using MediaBrowser.Controller.Dto;
|
using MediaBrowser.Controller.Dto;
|
||||||
using MediaBrowser.Controller.Entities;
|
using MediaBrowser.Controller.Entities;
|
||||||
@ -72,7 +73,7 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
/// <param name="userId">User id.</param>
|
/// <param name="userId">User id.</param>
|
||||||
/// <param name="itemId">Item id.</param>
|
/// <param name="itemId">Item id.</param>
|
||||||
/// <response code="200">Item returned.</response>
|
/// <response code="200">Item returned.</response>
|
||||||
/// <returns>An <see cref="OkResult"/> containing the d item.</returns>
|
/// <returns>An <see cref="OkResult"/> containing the item.</returns>
|
||||||
[HttpGet("Users/{userId}/Items/{itemId}")]
|
[HttpGet("Users/{userId}/Items/{itemId}")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
||||||
@ -86,11 +87,19 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
var item = itemId.Equals(default)
|
var item = itemId.Equals(default)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: _libraryManager.GetItemById(itemId);
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
|
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions().AddClientFields(User);
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
@ -139,11 +148,19 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
var item = itemId.Equals(default)
|
var item = itemId.Equals(default)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: _libraryManager.GetItemById(itemId);
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
|
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
|
||||||
var dtoOptions = new DtoOptions().AddClientFields(User);
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
|
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
|
||||||
@ -162,7 +179,29 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
||||||
{
|
{
|
||||||
return MarkFavorite(userId, itemId, true);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = itemId.Equals(default)
|
||||||
|
? _libraryManager.GetUserRootFolder()
|
||||||
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarkFavorite(user, item, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -176,7 +215,29 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
||||||
{
|
{
|
||||||
return MarkFavorite(userId, itemId, false);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = itemId.Equals(default)
|
||||||
|
? _libraryManager.GetUserRootFolder()
|
||||||
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return MarkFavorite(user, item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -190,7 +251,29 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
|
||||||
{
|
{
|
||||||
return UpdateUserItemRatingInternal(userId, itemId, null);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = itemId.Equals(default)
|
||||||
|
? _libraryManager.GetUserRootFolder()
|
||||||
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateUserItemRatingInternal(user, item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -205,7 +288,29 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
|
public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
|
||||||
{
|
{
|
||||||
return UpdateUserItemRatingInternal(userId, itemId, likes);
|
var user = _userManager.GetUserById(userId);
|
||||||
|
if (user is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = itemId.Equals(default)
|
||||||
|
? _libraryManager.GetUserRootFolder()
|
||||||
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
|
if (item is null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return UpdateUserItemRatingInternal(user, item, likes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -228,13 +333,20 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
var item = itemId.Equals(default)
|
var item = itemId.Equals(default)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: _libraryManager.GetItemById(itemId);
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions().AddClientFields(User);
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
if (item is IHasTrailers hasTrailers)
|
if (item is IHasTrailers hasTrailers)
|
||||||
{
|
{
|
||||||
var trailers = hasTrailers.LocalTrailers;
|
var trailers = hasTrailers.LocalTrailers;
|
||||||
@ -266,11 +378,19 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
var item = itemId.Equals(default)
|
var item = itemId.Equals(default)
|
||||||
? _libraryManager.GetUserRootFolder()
|
? _libraryManager.GetUserRootFolder()
|
||||||
: _libraryManager.GetItemById(itemId);
|
: _libraryManager.GetItemById(itemId);
|
||||||
|
|
||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
var dtoOptions = new DtoOptions().AddClientFields(User);
|
var dtoOptions = new DtoOptions().AddClientFields(User);
|
||||||
|
|
||||||
return Ok(item
|
return Ok(item
|
||||||
@ -385,15 +505,11 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the favorite.
|
/// Marks the favorite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="user">The user.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
|
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
|
||||||
private UserItemDataDto MarkFavorite(Guid userId, Guid itemId, bool isFavorite)
|
private UserItemDataDto MarkFavorite(User user, BaseItem item, bool isFavorite)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(userId);
|
|
||||||
|
|
||||||
var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
|
|
||||||
|
|
||||||
// Get the user data for this item
|
// Get the user data for this item
|
||||||
var data = _userDataRepository.GetUserData(user, item);
|
var data = _userDataRepository.GetUserData(user, item);
|
||||||
|
|
||||||
@ -408,15 +524,11 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the user item rating.
|
/// Updates the user item rating.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="userId">The user id.</param>
|
/// <param name="user">The user.</param>
|
||||||
/// <param name="itemId">The item id.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="likes">if set to <c>true</c> [likes].</param>
|
/// <param name="likes">if set to <c>true</c> [likes].</param>
|
||||||
private UserItemDataDto UpdateUserItemRatingInternal(Guid userId, Guid itemId, bool? likes)
|
private UserItemDataDto UpdateUserItemRatingInternal(User user, BaseItem item, bool? likes)
|
||||||
{
|
{
|
||||||
var user = _userManager.GetUserById(userId);
|
|
||||||
|
|
||||||
var item = itemId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(itemId);
|
|
||||||
|
|
||||||
// Get the user data for this item
|
// Get the user data for this item
|
||||||
var data = _userDataRepository.GetUserData(user, item);
|
var data = _userDataRepository.GetUserData(user, item);
|
||||||
|
|
||||||
@ -455,6 +567,13 @@ public class UserLibraryController : BaseJellyfinApiController
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item is not UserRootFolder
|
||||||
|
// Check the item is visible for the user
|
||||||
|
&& !item.IsVisible(user))
|
||||||
|
{
|
||||||
|
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
|
||||||
|
}
|
||||||
|
|
||||||
var result = await _lyricManager.GetLyrics(item).ConfigureAwait(false);
|
var result = await _lyricManager.GetLyrics(item).ConfigureAwait(false);
|
||||||
if (result is not null)
|
if (result is not null)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,8 @@ namespace Jellyfin.Server.Migrations
|
|||||||
private static readonly Type[] _preStartupMigrationTypes =
|
private static readonly Type[] _preStartupMigrationTypes =
|
||||||
{
|
{
|
||||||
typeof(PreStartupRoutines.CreateNetworkConfiguration),
|
typeof(PreStartupRoutines.CreateNetworkConfiguration),
|
||||||
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout)
|
typeof(PreStartupRoutines.MigrateMusicBrainzTimeout),
|
||||||
|
typeof(PreStartupRoutines.MigrateRatingLevels)
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Emby.Server.Implementations;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using SQLitePCL.pretty;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Migrations.PreStartupRoutines
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Migrate rating levels to new rating level system.
|
||||||
|
/// </summary>
|
||||||
|
internal class MigrateRatingLevels : IMigrationRoutine
|
||||||
|
{
|
||||||
|
private const string DbFilename = "library.db";
|
||||||
|
private readonly ILogger<MigrateRatingLevels> _logger;
|
||||||
|
private readonly IServerApplicationPaths _applicationPaths;
|
||||||
|
|
||||||
|
public MigrateRatingLevels(ServerApplicationPaths applicationPaths, ILoggerFactory loggerFactory)
|
||||||
|
{
|
||||||
|
_applicationPaths = applicationPaths;
|
||||||
|
_logger = loggerFactory.CreateLogger<MigrateRatingLevels>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Guid Id => Guid.Parse("{67445D54-B895-4B24-9F4C-35CE0690EA07}");
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Name => "MigrateRatingLevels";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Perform()
|
||||||
|
{
|
||||||
|
var dataPath = _applicationPaths.DataPath;
|
||||||
|
var dbPath = Path.Combine(dataPath, DbFilename);
|
||||||
|
using (var connection = SQLite3.Open(
|
||||||
|
dbPath,
|
||||||
|
ConnectionFlags.ReadWrite,
|
||||||
|
null))
|
||||||
|
{
|
||||||
|
// Back up the database before deleting any entries
|
||||||
|
for (int i = 1; ; i++)
|
||||||
|
{
|
||||||
|
var bakPath = string.Format(CultureInfo.InvariantCulture, "{0}.bak{1}", dbPath, i);
|
||||||
|
if (!File.Exists(bakPath))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Copy(dbPath, bakPath);
|
||||||
|
_logger.LogInformation("Library database backed up to {BackupPath}", bakPath);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Cannot make a backup of {Library} at path {BackupPath}", DbFilename, bakPath);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate parental rating levels to new schema
|
||||||
|
_logger.LogInformation("Migrating parental rating levels.");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE OfficialRating = 'NR'");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = ''");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = NULL WHERE InheritedParentalRatingValue = 0");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 100");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 1000 WHERE InheritedParentalRatingValue = 15");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 10");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 18 WHERE InheritedParentalRatingValue = 9");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 16 WHERE InheritedParentalRatingValue = 8");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 7");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 6");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 12 WHERE InheritedParentalRatingValue = 5");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 7 WHERE InheritedParentalRatingValue = 4");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 3");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 6 WHERE InheritedParentalRatingValue = 2");
|
||||||
|
connection.Execute("UPDATE TypedBaseItems SET InheritedParentalRatingValue = 0 WHERE InheritedParentalRatingValue = 1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -554,7 +554,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
public string OfficialRating { get; set; }
|
public string OfficialRating { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public int InheritedParentalRatingValue { get; set; }
|
public int? InheritedParentalRatingValue { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the critic rating.
|
/// Gets or sets the critic rating.
|
||||||
@ -1534,12 +1534,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
var maxAllowedRating = user.MaxParentalAgeRating;
|
var maxAllowedRating = user.MaxParentalAgeRating;
|
||||||
|
|
||||||
if (maxAllowedRating is null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var rating = CustomRatingForComparison;
|
var rating = CustomRatingForComparison;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(rating))
|
if (string.IsNullOrEmpty(rating))
|
||||||
@ -1549,12 +1543,13 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(rating))
|
if (string.IsNullOrEmpty(rating))
|
||||||
{
|
{
|
||||||
|
Logger.LogDebug("{0} has no parental rating set.", Name);
|
||||||
return !GetBlockUnratedValue(user);
|
return !GetBlockUnratedValue(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = LocalizationManager.GetRatingLevel(rating);
|
var value = LocalizationManager.GetRatingLevel(rating);
|
||||||
|
|
||||||
// Could not determine the integer value
|
// Could not determine rating level
|
||||||
if (!value.HasValue)
|
if (!value.HasValue)
|
||||||
{
|
{
|
||||||
var isAllowed = !GetBlockUnratedValue(user);
|
var isAllowed = !GetBlockUnratedValue(user);
|
||||||
@ -1567,7 +1562,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return isAllowed;
|
return isAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value.Value <= maxAllowedRating.Value;
|
return !maxAllowedRating.HasValue || value.Value <= maxAllowedRating.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int? GetInheritedParentalRatingValue()
|
public int? GetInheritedParentalRatingValue()
|
||||||
@ -1627,10 +1622,10 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the block unrated value.
|
/// Gets a bool indicating if access to the unrated item is blocked or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The configuration.</param>
|
/// <param name="user">The configuration.</param>
|
||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if blocked, <c>false</c> otherwise.</returns>
|
||||||
protected virtual bool GetBlockUnratedValue(User user)
|
protected virtual bool GetBlockUnratedValue(User user)
|
||||||
{
|
{
|
||||||
// Don't block plain folders that are unrated. Let the media underneath get blocked
|
// Don't block plain folders that are unrated. Let the media underneath get blocked
|
||||||
@ -2517,7 +2512,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
var item = this;
|
var item = this;
|
||||||
|
|
||||||
var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0;
|
var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? null;
|
||||||
if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
|
if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
|
||||||
{
|
{
|
||||||
item.InheritedParentalRatingValue = inheritedParentalRatingValue;
|
item.InheritedParentalRatingValue = inheritedParentalRatingValue;
|
||||||
|
@ -308,6 +308,11 @@ namespace MediaBrowser.Controller.Entities.TV
|
|||||||
id.SeriesDisplayOrder = series.DisplayOrder;
|
id.SeriesDisplayOrder = series.DisplayOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Season is not null)
|
||||||
|
{
|
||||||
|
id.SeasonProviderIds = Season.ProviderIds;
|
||||||
|
}
|
||||||
|
|
||||||
id.IsMissingEpisode = IsMissingEpisode;
|
id.IsMissingEpisode = IsMissingEpisode;
|
||||||
id.IndexNumberEnd = IndexNumberEnd;
|
id.IndexNumberEnd = IndexNumberEnd;
|
||||||
|
|
||||||
|
@ -71,6 +71,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
"m4v",
|
"m4v",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Set max transcoding channels for encoders that can't handle more than a set amount of channels
|
||||||
|
// AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
|
||||||
|
private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ "wmav2", 2 },
|
||||||
|
{ "libmp3lame", 2 },
|
||||||
|
{ "libfdk_aac", 6 },
|
||||||
|
{ "aac_at", 6 },
|
||||||
|
{ "ac3", 6 },
|
||||||
|
{ "eac3", 6 },
|
||||||
|
{ "dca", 6 },
|
||||||
|
{ "mlp", 6 },
|
||||||
|
{ "truehd", 6 },
|
||||||
|
};
|
||||||
|
|
||||||
public EncodingHelper(
|
public EncodingHelper(
|
||||||
IApplicationPaths appPaths,
|
IApplicationPaths appPaths,
|
||||||
IMediaEncoder mediaEncoder,
|
IMediaEncoder mediaEncoder,
|
||||||
@ -2231,25 +2246,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
if (isTranscodingAudio)
|
if (isTranscodingAudio)
|
||||||
{
|
{
|
||||||
// Set max transcoding channels for encoders that can't handle more than a set amount of channels
|
var audioEncoder = GetAudioEncoder(state);
|
||||||
// AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
|
if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
|
||||||
int transcoderChannelLimit = GetAudioEncoder(state) switch
|
|
||||||
{
|
{
|
||||||
string audioEncoder when audioEncoder.Equals("wmav2", StringComparison.OrdinalIgnoreCase)
|
// Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels.
|
||||||
|| audioEncoder.Equals("libmp3lame", StringComparison.OrdinalIgnoreCase) => 2,
|
transcoderChannelLimit = 8;
|
||||||
string audioEncoder when audioEncoder.Equals("libfdk_aac", StringComparison.OrdinalIgnoreCase)
|
}
|
||||||
|| audioEncoder.Equals("aac_at", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| audioEncoder.Equals("ac3", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| audioEncoder.Equals("eac3", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| audioEncoder.Equals("dts", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| audioEncoder.Equals("mlp", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| audioEncoder.Equals("truehd", StringComparison.OrdinalIgnoreCase) => 6,
|
|
||||||
// Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels
|
|
||||||
_ => 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit
|
// Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit
|
||||||
|
|
||||||
resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit;
|
resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit;
|
||||||
|
|
||||||
if (request.TranscodingMaxAudioChannels < resultChannels)
|
if (request.TranscodingMaxAudioChannels < resultChannels)
|
||||||
@ -4228,12 +4232,12 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
subFilters.Add(subTextSubtitlesFilter);
|
subFilters.Add(subTextSubtitlesFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
subFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16");
|
// prefer vaapi hwupload to vulkan hwupload,
|
||||||
|
// Mesa RADV does not support a dedicated transfer queue.
|
||||||
|
subFilters.Add("hwupload=derive_device=vaapi,format=vaapi,hwmap=derive_device=vulkan");
|
||||||
|
|
||||||
overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
|
overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
|
||||||
|
overlayFilters.Add("scale_vulkan=format=nv12");
|
||||||
// explicitly sync using libplacebo.
|
|
||||||
overlayFilters.Add("libplacebo=format=nv12:upscaler=none:downscaler=none");
|
|
||||||
|
|
||||||
// OUTPUT vaapi(nv12/bgra) surface(vram)
|
// OUTPUT vaapi(nv12/bgra) surface(vram)
|
||||||
// reverse-mapping via vaapi-vulkan interop.
|
// reverse-mapping via vaapi-vulkan interop.
|
||||||
|
@ -232,6 +232,11 @@ namespace MediaBrowser.Controller.Net
|
|||||||
// TODO Investigate and properly fix.
|
// TODO Investigate and properly fix.
|
||||||
Logger.LogError(ex, "Object Disposed");
|
Logger.LogError(ex, "Object Disposed");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// TODO Investigate and properly fix.
|
||||||
|
Logger.LogError(ex, "Error disposing websocket");
|
||||||
|
}
|
||||||
|
|
||||||
lock (_activeConnections)
|
lock (_activeConnections)
|
||||||
{
|
{
|
||||||
|
@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Persistence
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="items">The items.</param>
|
/// <param name="items">The items.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken);
|
void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken);
|
||||||
|
|
||||||
void SaveImages(BaseItem item);
|
void SaveImages(BaseItem item);
|
||||||
|
|
||||||
|
@ -12,10 +12,13 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
public EpisodeInfo()
|
public EpisodeInfo()
|
||||||
{
|
{
|
||||||
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
SeasonProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<string, string> SeriesProviderIds { get; set; }
|
public Dictionary<string, string> SeriesProviderIds { get; set; }
|
||||||
|
|
||||||
|
public Dictionary<string, string> SeasonProviderIds { get; set; }
|
||||||
|
|
||||||
public int? IndexNumberEnd { get; set; }
|
public int? IndexNumberEnd { get; set; }
|
||||||
|
|
||||||
public bool IsMissingEpisode { get; set; }
|
public bool IsMissingEpisode { get; set; }
|
||||||
|
@ -26,6 +26,7 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
ReplaceAllMetadata = copy.ReplaceAllMetadata;
|
ReplaceAllMetadata = copy.ReplaceAllMetadata;
|
||||||
EnableRemoteContentProbe = copy.EnableRemoteContentProbe;
|
EnableRemoteContentProbe = copy.EnableRemoteContentProbe;
|
||||||
|
|
||||||
|
IsAutomated = copy.IsAutomated;
|
||||||
ImageRefreshMode = copy.ImageRefreshMode;
|
ImageRefreshMode = copy.ImageRefreshMode;
|
||||||
ReplaceAllImages = copy.ReplaceAllImages;
|
ReplaceAllImages = copy.ReplaceAllImages;
|
||||||
ReplaceImages = copy.ReplaceImages;
|
ReplaceImages = copy.ReplaceImages;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
@ -12,7 +12,7 @@ namespace MediaBrowser.Model.Entities
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParentalRating(string name, int value)
|
public ParentalRating(string name, int? value)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Value = value;
|
Value = value;
|
||||||
@ -28,6 +28,6 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// Gets or sets the value.
|
/// Gets or sets the value.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The value.</value>
|
/// <value>The value.</value>
|
||||||
public int Value { get; set; }
|
public int? Value { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Users
|
|||||||
LoginAttemptsBeforeLockout = -1;
|
LoginAttemptsBeforeLockout = -1;
|
||||||
|
|
||||||
MaxActiveSessions = 0;
|
MaxActiveSessions = 0;
|
||||||
|
MaxParentalRating = null;
|
||||||
|
|
||||||
EnableAllChannels = true;
|
EnableAllChannels = true;
|
||||||
EnabledChannels = Array.Empty<Guid>();
|
EnabledChannels = Array.Empty<Guid>();
|
||||||
|
@ -284,12 +284,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return new List<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
_logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
||||||
return new List<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ namespace MediaBrowser.Providers.Playlists
|
|||||||
return GetPlsItems(stream);
|
return GetPlsItems(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<LinkedChild>();
|
return Enumerable.Empty<LinkedChild>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<LinkedChild> GetPlsItems(Stream stream)
|
private IEnumerable<LinkedChild> GetPlsItems(Stream stream)
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -42,7 +43,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
return new ImageType[]
|
||||||
{
|
{
|
||||||
ImageType.Primary,
|
ImageType.Primary,
|
||||||
ImageType.Logo,
|
ImageType.Logo,
|
||||||
@ -74,7 +75,7 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new List<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item)
|
private IEnumerable<RemoteImageInfo> GetImages(AudioDbArtistProvider.Artist item)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>AudioDB</title>
|
<title>TheAudioDB</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
|
<h1>TheAudioDB</h1>
|
||||||
<form class="configForm">
|
<form class="configForm">
|
||||||
<label class="checkboxContainer">
|
<label class="checkboxContainer">
|
||||||
<input is="emby-checkbox" type="checkbox" id="replaceAlbumName" />
|
<input is="emby-checkbox" type="checkbox" id="replaceAlbumName" />
|
||||||
|
@ -7,16 +7,22 @@ namespace MediaBrowser.Providers.Plugins.MusicBrainz.Configuration;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class PluginConfiguration : BasePluginConfiguration
|
public class PluginConfiguration : BasePluginConfiguration
|
||||||
{
|
{
|
||||||
private const string DefaultServer = "https://musicbrainz.org";
|
/// <summary>
|
||||||
|
/// The default server URL.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultServer = "https://musicbrainz.org";
|
||||||
|
|
||||||
private const double DefaultRateLimit = 1.0;
|
/// <summary>
|
||||||
|
/// The default rate limit.
|
||||||
|
/// </summary>
|
||||||
|
public const double DefaultRateLimit = 1.0;
|
||||||
|
|
||||||
private string _server = DefaultServer;
|
private string _server = DefaultServer;
|
||||||
|
|
||||||
private double _rateLimit = DefaultRateLimit;
|
private double _rateLimit = DefaultRateLimit;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the server url.
|
/// Gets or sets the server URL.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Server
|
public string Server
|
||||||
{
|
{
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
<div id="musicBrainzConfigurationPage" data-role="page"
|
<!DOCTYPE html>
|
||||||
class="page type-interior pluginConfigurationPage musicBrainzConfigurationPage" data-require="emby-input,emby-button,emby-checkbox">
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>MusicBrainz</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
<h1>MusicBrainz</h1>
|
<h1>MusicBrainz</h1>
|
||||||
<form class="musicBrainzConfigurationForm">
|
<form class="configForm">
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" type="text" id="server" required label="Server" />
|
<input is="emby-input" type="text" id="server" required label="Server" />
|
||||||
<div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
|
<div class="fieldDescription">This can be a mirror of the official server or even a custom server.</div>
|
||||||
@ -28,7 +33,7 @@
|
|||||||
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
||||||
};
|
};
|
||||||
|
|
||||||
document.querySelector('.musicBrainzConfigurationPage')
|
document.querySelector('.configPage')
|
||||||
.addEventListener('pageshow', function () {
|
.addEventListener('pageshow', function () {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||||
@ -52,7 +57,7 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
document.querySelector('.musicBrainzConfigurationForm')
|
document.querySelector('.configForm')
|
||||||
.addEventListener('submit', function (e) {
|
.addEventListener('submit', function (e) {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
|
|||||||
{
|
{
|
||||||
// Fallback to official server
|
// Fallback to official server
|
||||||
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
|
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
|
||||||
var defaultServer = new Uri(configuration.Server);
|
var defaultServer = new Uri(PluginConfiguration.DefaultServer);
|
||||||
Query.DefaultServer = defaultServer.Host;
|
Query.DefaultServer = defaultServer.Host;
|
||||||
Query.DefaultPort = defaultServer.Port;
|
Query.DefaultPort = defaultServer.Port;
|
||||||
Query.DefaultUrlScheme = defaultServer.Scheme;
|
Query.DefaultUrlScheme = defaultServer.Scheme;
|
||||||
@ -157,10 +157,10 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
|
|||||||
var artists = releaseSearchResult.ArtistCredit;
|
var artists = releaseSearchResult.ArtistCredit;
|
||||||
if (artists is not null && artists.Count > 0)
|
if (artists is not null && artists.Count > 0)
|
||||||
{
|
{
|
||||||
var artistResults = new List<RemoteSearchResult>();
|
var artistResults = new RemoteSearchResult[artists.Count];
|
||||||
|
for (int i = 0; i < artists.Count; i++)
|
||||||
foreach (var artist in artists)
|
|
||||||
{
|
{
|
||||||
|
var artist = artists[i];
|
||||||
var artistResult = new RemoteSearchResult
|
var artistResult = new RemoteSearchResult
|
||||||
{
|
{
|
||||||
Name = artist.Name
|
Name = artist.Name
|
||||||
@ -171,11 +171,11 @@ public class MusicBrainzAlbumProvider : IRemoteMetadataProvider<MusicAlbum, Albu
|
|||||||
artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
|
artistResult.SetProviderId(MetadataProvider.MusicBrainzArtist, artist.Artist!.Id.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
artistResults.Add(artistResult);
|
artistResults[i] = artistResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchResult.AlbumArtist = artistResults[0];
|
searchResult.AlbumArtist = artistResults[0];
|
||||||
searchResult.Artists = artistResults.ToArray();
|
searchResult.Artists = artistResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
|
searchResult.SetProviderId(MetadataProvider.MusicBrainzAlbum, releaseSearchResult.Id.ToString());
|
||||||
|
@ -55,7 +55,7 @@ public class MusicBrainzArtistProvider : IRemoteMetadataProvider<MusicArtist, Ar
|
|||||||
{
|
{
|
||||||
// Fallback to official server
|
// Fallback to official server
|
||||||
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
|
_logger.LogWarning("Invalid MusicBrainz server specified, falling back to official server");
|
||||||
var defaultServer = new Uri(configuration.Server);
|
var defaultServer = new Uri(PluginConfiguration.DefaultServer);
|
||||||
Query.DefaultServer = defaultServer.Host;
|
Query.DefaultServer = defaultServer.Host;
|
||||||
Query.DefaultPort = defaultServer.Port;
|
Query.DefaultPort = defaultServer.Port;
|
||||||
Query.DefaultUrlScheme = defaultServer.Scheme;
|
Query.DefaultUrlScheme = defaultServer.Scheme;
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
<title>OMDb</title>
|
<title>OMDb</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
|
<h1>OMDb</h1>
|
||||||
<form class="configForm">
|
<form class="configForm">
|
||||||
<label class="checkboxContainer">
|
<label class="checkboxContainer">
|
||||||
<input is="emby-checkbox" type="checkbox" id="castAndCrew" />
|
<input is="emby-checkbox" type="checkbox" id="castAndCrew" />
|
||||||
|
@ -38,10 +38,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
|
|||||||
|
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
yield return ImageType.Primary;
|
||||||
{
|
|
||||||
ImageType.Primary
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken)
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
<title>Studio Images</title>
|
<title>Studio Images</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
|
<h1>Studio Images</h1>
|
||||||
<form class="configForm">
|
<form class="configForm">
|
||||||
<div class="inputContainer">
|
<div class="inputContainer">
|
||||||
<input is="emby-input" type="text" id="repository" label="Repository" />
|
<input is="emby-input" type="text" id="repository" label="Repository" />
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -50,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
return new ImageType[]
|
||||||
{
|
{
|
||||||
ImageType.Primary,
|
ImageType.Primary,
|
||||||
ImageType.Backdrop
|
ImageType.Backdrop
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -74,7 +72,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
|||||||
|
|
||||||
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
|
var collectionSearchResults = await _tmdbClientManager.SearchCollectionAsync(searchInfo.Name, language, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var collections = new List<RemoteSearchResult>();
|
var collections = new RemoteSearchResult[collectionSearchResults.Count];
|
||||||
for (var i = 0; i < collectionSearchResults.Count; i++)
|
for (var i = 0; i < collectionSearchResults.Count; i++)
|
||||||
{
|
{
|
||||||
var collection = new RemoteSearchResult
|
var collection = new RemoteSearchResult
|
||||||
@ -84,7 +82,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
|||||||
};
|
};
|
||||||
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
|
collection.SetProviderId(MetadataProvider.Tmdb, collectionSearchResults[i].Id.ToString(CultureInfo.InvariantCulture));
|
||||||
|
|
||||||
collections.Add(collection);
|
collections[i] = collection;
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections;
|
return collections;
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
<title>TMDb</title>
|
<title>TMDb</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
<div id="configPage" data-role="page" class="page type-interior pluginConfigurationPage configPage" data-require="emby-input,emby-button,emby-checkbox">
|
||||||
<div data-role="content">
|
<div data-role="content">
|
||||||
<div class="content-primary">
|
<div class="content-primary">
|
||||||
|
<h1>TMDb</h1>
|
||||||
<form class="configForm">
|
<form class="configForm">
|
||||||
<label class="checkboxContainer">
|
<label class="checkboxContainer">
|
||||||
<input is="emby-checkbox" type="checkbox" id="includeAdult" />
|
<input is="emby-checkbox" type="checkbox" id="includeAdult" />
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -51,7 +49,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
return new ImageType[]
|
||||||
{
|
{
|
||||||
ImageType.Primary,
|
ImageType.Primary,
|
||||||
ImageType.Backdrop,
|
ImageType.Backdrop,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -64,6 +62,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
cancellationToken)
|
cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (movie is not null)
|
||||||
|
{
|
||||||
var remoteResult = new RemoteSearchResult
|
var remoteResult = new RemoteSearchResult
|
||||||
{
|
{
|
||||||
Name = movie.Title ?? movie.OriginalTitle,
|
Name = movie.Title ?? movie.OriginalTitle,
|
||||||
@ -88,8 +88,9 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
|
|
||||||
return new[] { remoteResult };
|
return new[] { remoteResult };
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IReadOnlyList<SearchMovie> movieResults;
|
IReadOnlyList<SearchMovie>? movieResults = null;
|
||||||
if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
|
if (searchInfo.TryGetProviderId(MetadataProvider.Imdb, out id))
|
||||||
{
|
{
|
||||||
var result = await _tmdbClientManager.FindByExternalIdAsync(
|
var result = await _tmdbClientManager.FindByExternalIdAsync(
|
||||||
@ -97,18 +98,20 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
|||||||
FindExternalSource.Imdb,
|
FindExternalSource.Imdb,
|
||||||
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
|
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
movieResults = result.MovieResults;
|
movieResults = result?.MovieResults;
|
||||||
}
|
}
|
||||||
else if (searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
|
|
||||||
|
if (movieResults is null && searchInfo.TryGetProviderId(MetadataProvider.Tvdb, out id))
|
||||||
{
|
{
|
||||||
var result = await _tmdbClientManager.FindByExternalIdAsync(
|
var result = await _tmdbClientManager.FindByExternalIdAsync(
|
||||||
id,
|
id,
|
||||||
FindExternalSource.TvDb,
|
FindExternalSource.TvDb,
|
||||||
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
|
TmdbUtils.GetImageLanguagesParam(searchInfo.MetadataLanguage),
|
||||||
cancellationToken).ConfigureAwait(false);
|
cancellationToken).ConfigureAwait(false);
|
||||||
movieResults = result.MovieResults;
|
movieResults = result?.MovieResults;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (movieResults is null)
|
||||||
{
|
{
|
||||||
movieResults = await _tmdbClientManager
|
movieResults = await _tmdbClientManager
|
||||||
.SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
|
.SearchMovieAsync(searchInfo.Name, searchInfo.Year ?? 0, searchInfo.MetadataLanguage, cancellationToken)
|
||||||
|
@ -46,10 +46,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
yield return ImageType.Primary;
|
||||||
{
|
|
||||||
ImageType.Primary
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -69,7 +67,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||||||
|
|
||||||
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
var personSearchResult = await _tmdbClientManager.SearchPersonAsync(searchInfo.Name, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var remoteSearchResults = new List<RemoteSearchResult>();
|
var remoteSearchResults = new RemoteSearchResult[personSearchResult.Count];
|
||||||
for (var i = 0; i < personSearchResult.Count; i++)
|
for (var i = 0; i < personSearchResult.Count; i++)
|
||||||
{
|
{
|
||||||
var person = personSearchResult[i];
|
var person = personSearchResult[i];
|
||||||
@ -81,7 +79,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||||||
};
|
};
|
||||||
|
|
||||||
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
remoteSearchResult.SetProviderId(MetadataProvider.Tmdb, person.Id.ToString(CultureInfo.InvariantCulture));
|
||||||
remoteSearchResults.Add(remoteSearchResult);
|
remoteSearchResults[i] = remoteSearchResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
return remoteSearchResults;
|
return remoteSearchResults;
|
||||||
@ -107,6 +105,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
|||||||
if (personTmdbId > 0)
|
if (personTmdbId > 0)
|
||||||
{
|
{
|
||||||
var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
var person = await _tmdbClientManager.GetPersonAsync(personTmdbId, info.MetadataLanguage, cancellationToken).ConfigureAwait(false);
|
||||||
|
if (person is null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
result.HasMetadata = true;
|
result.HasMetadata = true;
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -49,10 +47,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
yield return ImageType.Primary;
|
||||||
{
|
|
||||||
ImageType.Primary
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -63,7 +58,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
|
|
||||||
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
var seriesTmdbId = Convert.ToInt32(series?.GetProviderId(MetadataProvider.Tmdb), CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
if (seriesTmdbId <= 0)
|
if (series is null || seriesTmdbId <= 0)
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<RemoteImageInfo>();
|
return Enumerable.Empty<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -87,7 +85,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
return metadataResult;
|
return metadataResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string tmdbId);
|
info.SeriesProviderIds.TryGetValue(MetadataProvider.Tmdb.ToString(), out string? tmdbId);
|
||||||
|
|
||||||
var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture);
|
var seriesTmdbId = Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture);
|
||||||
if (seriesTmdbId <= 0)
|
if (seriesTmdbId <= 0)
|
||||||
|
@ -48,10 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
yield return ImageType.Primary;
|
||||||
{
|
|
||||||
ImageType.Primary
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
|
||||||
{
|
{
|
||||||
return new List<ImageType>
|
return new ImageType[]
|
||||||
{
|
{
|
||||||
ImageType.Primary,
|
ImageType.Primary,
|
||||||
ImageType.Backdrop,
|
ImageType.Backdrop,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@ -211,7 +209,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(tmdbId))
|
if (!int.TryParse(tmdbId, CultureInfo.InvariantCulture, out int tmdbIdInt))
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -219,9 +217,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
|||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var tvShow = await _tmdbClientManager
|
var tvShow = await _tmdbClientManager
|
||||||
.GetSeriesAsync(Convert.ToInt32(tmdbId, CultureInfo.InvariantCulture), info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
.GetSeriesAsync(tmdbIdInt, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (tvShow is null)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
result = new MetadataResult<Series>
|
result = new MetadataResult<Series>
|
||||||
{
|
{
|
||||||
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
|
Item = MapTvShowToSeries(tvShow, info.MetadataCountryCode),
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
#nullable disable
|
using System;
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -50,10 +48,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb movie or null if not found.</returns>
|
/// <returns>The TMDb movie or null if not found.</returns>
|
||||||
public async Task<Movie> GetMovieAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
public async Task<Movie?> GetMovieAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"movie-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out Movie movie))
|
if (_memoryCache.TryGetValue(key, out Movie? movie))
|
||||||
{
|
{
|
||||||
return movie;
|
return movie;
|
||||||
}
|
}
|
||||||
@ -89,10 +87,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb collection or null if not found.</returns>
|
/// <returns>The TMDb collection or null if not found.</returns>
|
||||||
public async Task<Collection> GetCollectionAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
public async Task<Collection?> GetCollectionAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"collection-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out Collection collection))
|
if (_memoryCache.TryGetValue(key, out Collection? collection))
|
||||||
{
|
{
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
@ -122,10 +120,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb tv show information or null if not found.</returns>
|
/// <returns>The TMDb tv show information or null if not found.</returns>
|
||||||
public async Task<TvShow> GetSeriesAsync(int tmdbId, string language, string imageLanguages, CancellationToken cancellationToken)
|
public async Task<TvShow?> GetSeriesAsync(int tmdbId, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"series-{tmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out TvShow series))
|
if (_memoryCache.TryGetValue(key, out TvShow? series))
|
||||||
{
|
{
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
@ -162,7 +160,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb tv show episode group information or null if not found.</returns>
|
/// <returns>The TMDb tv show episode group information or null if not found.</returns>
|
||||||
private async Task<TvGroupCollection> GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken)
|
private async Task<TvGroupCollection?> GetSeriesGroupAsync(int tvShowId, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
TvGroupType? groupType =
|
TvGroupType? groupType =
|
||||||
string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate :
|
string.Equals(displayOrder, "originalAirDate", StringComparison.Ordinal) ? TvGroupType.OriginalAirDate :
|
||||||
@ -180,7 +178,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
}
|
}
|
||||||
|
|
||||||
var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
|
var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out TvGroupCollection group))
|
if (_memoryCache.TryGetValue(key, out TvGroupCollection? group))
|
||||||
{
|
{
|
||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
@ -217,10 +215,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb tv season information or null if not found.</returns>
|
/// <returns>The TMDb tv season information or null if not found.</returns>
|
||||||
public async Task<TvSeason> GetSeasonAsync(int tvShowId, int seasonNumber, string language, string imageLanguages, CancellationToken cancellationToken)
|
public async Task<TvSeason?> GetSeasonAsync(int tvShowId, int seasonNumber, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"season-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out TvSeason season))
|
if (_memoryCache.TryGetValue(key, out TvSeason? season))
|
||||||
{
|
{
|
||||||
return season;
|
return season;
|
||||||
}
|
}
|
||||||
@ -254,10 +252,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb tv episode information or null if not found.</returns>
|
/// <returns>The TMDb tv episode information or null if not found.</returns>
|
||||||
public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken)
|
public async Task<TvEpisode?> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string? language, string? imageLanguages, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
|
var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out TvEpisode episode))
|
if (_memoryCache.TryGetValue(key, out TvEpisode? episode))
|
||||||
{
|
{
|
||||||
return episode;
|
return episode;
|
||||||
}
|
}
|
||||||
@ -301,10 +299,10 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="language">The episode's language.</param>
|
/// <param name="language">The episode's language.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb person information or null if not found.</returns>
|
/// <returns>The TMDb person information or null if not found.</returns>
|
||||||
public async Task<Person> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
|
public async Task<Person?> GetPersonAsync(int personTmdbId, string language, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"person-{personTmdbId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out Person person))
|
if (_memoryCache.TryGetValue(key, out Person? person))
|
||||||
{
|
{
|
||||||
return person;
|
return person;
|
||||||
}
|
}
|
||||||
@ -333,14 +331,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="language">The item's language.</param>
|
/// <param name="language">The item's language.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <returns>The TMDb item or null if not found.</returns>
|
/// <returns>The TMDb item or null if not found.</returns>
|
||||||
public async Task<FindContainer> FindByExternalIdAsync(
|
public async Task<FindContainer?> FindByExternalIdAsync(
|
||||||
string externalId,
|
string externalId,
|
||||||
FindExternalSource source,
|
FindExternalSource source,
|
||||||
string language,
|
string language,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"find-{source.ToString()}-{externalId.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out FindContainer result))
|
if (_memoryCache.TryGetValue(key, out FindContainer? result))
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -372,7 +370,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<SearchTv>> SearchSeriesAsync(string name, string language, int year = 0, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var key = $"searchseries-{name}-{language}";
|
var key = $"searchseries-{name}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv> series))
|
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchTv>? series) && series is not null)
|
||||||
{
|
{
|
||||||
return series.Results;
|
return series.Results;
|
||||||
}
|
}
|
||||||
@ -400,7 +398,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<SearchPerson>> SearchPersonAsync(string name, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"searchperson-{name}";
|
var key = $"searchperson-{name}";
|
||||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson> person))
|
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchPerson>? person) && person is not null)
|
||||||
{
|
{
|
||||||
return person.Results;
|
return person.Results;
|
||||||
}
|
}
|
||||||
@ -442,7 +440,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<SearchMovie>> SearchMovieAsync(string name, int year, string language, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
|
var key = $"moviesearch-{name}-{year.ToString(CultureInfo.InvariantCulture)}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie> movies))
|
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchMovie>? movies) && movies is not null)
|
||||||
{
|
{
|
||||||
return movies.Results;
|
return movies.Results;
|
||||||
}
|
}
|
||||||
@ -471,7 +469,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
|
public async Task<IReadOnlyList<SearchCollection>> SearchCollectionAsync(string name, string language, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var key = $"collectionsearch-{name}-{language}";
|
var key = $"collectionsearch-{name}-{language}";
|
||||||
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection> collections))
|
if (_memoryCache.TryGetValue(key, out SearchContainer<SearchCollection>? collections) && collections is not null)
|
||||||
{
|
{
|
||||||
return collections.Results;
|
return collections.Results;
|
||||||
}
|
}
|
||||||
@ -496,7 +494,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="size">The image size to fetch.</param>
|
/// <param name="size">The image size to fetch.</param>
|
||||||
/// <param name="path">The relative URL of the image.</param>
|
/// <param name="path">The relative URL of the image.</param>
|
||||||
/// <returns>The absolute URL.</returns>
|
/// <returns>The absolute URL.</returns>
|
||||||
private string GetUrl(string size, string path)
|
private string? GetUrl(string? size, string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path))
|
if (string.IsNullOrEmpty(path))
|
||||||
{
|
{
|
||||||
@ -511,7 +509,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="posterPath">The relative URL of the poster.</param>
|
/// <param name="posterPath">The relative URL of the poster.</param>
|
||||||
/// <returns>The absolute URL.</returns>
|
/// <returns>The absolute URL.</returns>
|
||||||
public string GetPosterUrl(string posterPath)
|
public string? GetPosterUrl(string posterPath)
|
||||||
{
|
{
|
||||||
return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath);
|
return GetUrl(Plugin.Instance.Configuration.PosterSize, posterPath);
|
||||||
}
|
}
|
||||||
@ -521,7 +519,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="actorProfilePath">The relative URL of the profile image.</param>
|
/// <param name="actorProfilePath">The relative URL of the profile image.</param>
|
||||||
/// <returns>The absolute URL.</returns>
|
/// <returns>The absolute URL.</returns>
|
||||||
public string GetProfileUrl(string actorProfilePath)
|
public string? GetProfileUrl(string actorProfilePath)
|
||||||
{
|
{
|
||||||
return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath);
|
return GetUrl(Plugin.Instance.Configuration.ProfileSize, actorProfilePath);
|
||||||
}
|
}
|
||||||
@ -579,7 +577,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// <param name="type">The type of the image.</param>
|
/// <param name="type">The type of the image.</param>
|
||||||
/// <param name="requestLanguage">The requested language.</param>
|
/// <param name="requestLanguage">The requested language.</param>
|
||||||
/// <returns>The remote images.</returns>
|
/// <returns>The remote images.</returns>
|
||||||
private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string size, ImageType type, string requestLanguage)
|
private IEnumerable<RemoteImageInfo> ConvertToRemoteImageInfo(IReadOnlyList<ImageData> images, string? size, ImageType type, string requestLanguage)
|
||||||
{
|
{
|
||||||
// sizes provided are for original resolution, don't store them when downloading scaled images
|
// sizes provided are for original resolution, don't store them when downloading scaled images
|
||||||
var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase);
|
var scaleImage = !string.Equals(size, "original", StringComparison.OrdinalIgnoreCase);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
using TMDbLib.Objects.General;
|
using TMDbLib.Objects.General;
|
||||||
@ -128,7 +129,8 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="language">The language code.</param>
|
/// <param name="language">The language code.</param>
|
||||||
/// <returns>The normalized language code.</returns>
|
/// <returns>The normalized language code.</returns>
|
||||||
public static string NormalizeLanguage(string language)
|
[return: NotNullIfNotNull(nameof(language))]
|
||||||
|
public static string? NormalizeLanguage(string? language)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(language))
|
if (string.IsNullOrEmpty(language))
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -56,7 +54,7 @@ namespace MediaBrowser.Providers.Subtitles
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure;
|
public event EventHandler<SubtitleDownloadFailureEventArgs>? SubtitleDownloadFailure;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<RemoteSubtitleInfo[]> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
|
public async Task<RemoteSubtitleInfo[]> SearchSubtitles(SubtitleSearchRequest request, CancellationToken cancellationToken)
|
||||||
@ -235,7 +233,7 @@ namespace MediaBrowser.Providers.Subtitles
|
|||||||
|
|
||||||
private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
|
private async Task TrySaveToFiles(Stream stream, List<string> savePaths)
|
||||||
{
|
{
|
||||||
List<Exception> exs = null;
|
List<Exception>? exs = null;
|
||||||
|
|
||||||
foreach (var savePath in savePaths)
|
foreach (var savePath in savePaths)
|
||||||
{
|
{
|
||||||
@ -245,7 +243,7 @@ namespace MediaBrowser.Providers.Subtitles
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
|
Directory.CreateDirectory(Path.GetDirectoryName(savePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
|
||||||
|
|
||||||
var fileOptions = AsyncFile.WriteOptions;
|
var fileOptions = AsyncFile.WriteOptions;
|
||||||
fileOptions.Mode = FileMode.CreateNew;
|
fileOptions.Mode = FileMode.CreateNew;
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#nullable disable
|
|
||||||
|
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -12,7 +12,7 @@ namespace Jellyfin.Extensions
|
|||||||
{
|
{
|
||||||
// Matches non-conforming unicode chars
|
// Matches non-conforming unicode chars
|
||||||
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
|
// https://mnaoumov.wordpress.com/2014/06/14/stripping-invalid-characters-from-utf-16-strings/
|
||||||
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)");
|
private static readonly Regex _nonConformingUnicode = new Regex("([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])|(\ufffd)", RegexOptions.Compiled);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes the diacritics character from the strings.
|
/// Removes the diacritics character from the strings.
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using System.Security.Claims;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AutoFixture;
|
using AutoFixture;
|
||||||
using AutoFixture.AutoMoq;
|
using AutoFixture.AutoMoq;
|
||||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
|
using Jellyfin.Data.Entities;
|
||||||
using Jellyfin.Server.Implementations.Security;
|
using Jellyfin.Server.Implementations.Security;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
@ -51,6 +55,32 @@ namespace Jellyfin.Api.Tests.Auth.DefaultAuthorizationPolicy
|
|||||||
Assert.True(context.HasSucceeded);
|
Assert.True(context.HasSucceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ShouldSucceedOnApiKey()
|
||||||
|
{
|
||||||
|
TestHelpers.SetupConfigurationManager(_configurationManagerMock, true);
|
||||||
|
|
||||||
|
_httpContextAccessor
|
||||||
|
.Setup(h => h.HttpContext!.Connection.RemoteIpAddress)
|
||||||
|
.Returns(new IPAddress(0));
|
||||||
|
|
||||||
|
_userManagerMock
|
||||||
|
.Setup(u => u.GetUserById(It.IsAny<Guid>()))
|
||||||
|
.Returns<User>(null);
|
||||||
|
|
||||||
|
var claims = new[]
|
||||||
|
{
|
||||||
|
new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
|
||||||
|
};
|
||||||
|
|
||||||
|
var identity = new ClaimsIdentity(claims, string.Empty);
|
||||||
|
var principal = new ClaimsPrincipal(identity);
|
||||||
|
var context = new AuthorizationHandlerContext(_requirements, principal, null);
|
||||||
|
|
||||||
|
await _sut.HandleAsync(context);
|
||||||
|
Assert.True(context.HasSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
|
[MemberData(nameof(GetParts_ValidAuthHeader_Success_Data))]
|
||||||
public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)
|
public void GetParts_ValidAuthHeader_Success(string input, Dictionary<string, string> parts)
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
using Emby.Dlna.Server;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Jellyfin.Dlna.Server.Tests;
|
||||||
|
|
||||||
|
public class DescriptionXmlBuilderTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetFriendlyName_EmptyProfile_ReturnsServerName()
|
||||||
|
{
|
||||||
|
const string ServerName = "Test Server Name";
|
||||||
|
var builder = new DescriptionXmlBuilder(new DeviceProfile(), "serverUdn", "localhost", ServerName, string.Empty);
|
||||||
|
Assert.Equal(ServerName, builder.GetFriendlyName());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFriendlyName_FriendlyName_ReturnsFriendlyName()
|
||||||
|
{
|
||||||
|
const string FriendlyName = "Friendly Neighborhood Test Server";
|
||||||
|
var builder = new DescriptionXmlBuilder(
|
||||||
|
new DeviceProfile()
|
||||||
|
{
|
||||||
|
FriendlyName = FriendlyName
|
||||||
|
},
|
||||||
|
"serverUdn",
|
||||||
|
"localhost",
|
||||||
|
"Test Server Name",
|
||||||
|
string.Empty);
|
||||||
|
Assert.Equal(FriendlyName, builder.GetFriendlyName());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetFriendlyName_FriendlyNameInterpolation_ReturnsFriendlyName()
|
||||||
|
{
|
||||||
|
var builder = new DescriptionXmlBuilder(
|
||||||
|
new DeviceProfile()
|
||||||
|
{
|
||||||
|
FriendlyName = "Friendly Neighborhood ${HostName}"
|
||||||
|
},
|
||||||
|
"serverUdn",
|
||||||
|
"localhost",
|
||||||
|
"Test Server Name",
|
||||||
|
string.Empty);
|
||||||
|
Assert.Equal("Friendly Neighborhood TestServerName", builder.GetFriendlyName());
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user