Merge branch 'master' into tmdb-person-language

pull/6033/head
Bond-009 4 years ago committed by GitHub
commit a7b7ff000b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -20,7 +20,7 @@ jobs:
with: with:
project: Current Release project: Current Release
action: delete action: delete
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Release Next' project - name: Add to 'Release Next' project
uses: alex-page/github-project-automation-plus@v0.7.1 uses: alex-page/github-project-automation-plus@v0.7.1
@ -29,7 +29,7 @@ jobs:
with: with:
project: Release Next project: Release Next
column: In progress column: In progress
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add to 'Current Release' project - name: Add to 'Current Release' project
uses: alex-page/github-project-automation-plus@v0.7.1 uses: alex-page/github-project-automation-plus@v0.7.1
@ -38,7 +38,7 @@ jobs:
with: with:
project: Current Release project: Current Release
column: In progress column: In progress
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Check number of comments from the team member - name: Check number of comments from the team member
if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER' if: github.event.issue.pull_request == '' && github.event.comment.author_association == 'MEMBER'
@ -52,7 +52,7 @@ jobs:
with: with:
project: Issue Triage for Main Repo project: Issue Triage for Main Repo
column: Needs triage column: Needs triage
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}
- name: Add issue to triage project - name: Add issue to triage project
uses: alex-page/github-project-automation-plus@v0.7.1 uses: alex-page/github-project-automation-plus@v0.7.1
@ -61,4 +61,4 @@ jobs:
with: with:
project: Issue Triage for Main Repo project: Issue Triage for Main Repo
column: Pending response column: Pending response
repo-token: ${{ secrets.GH_TOKEN }} repo-token: ${{ secrets.JF_BOT_TOKEN }}

@ -14,4 +14,4 @@ jobs:
- uses: eps1lon/actions-label-merge-conflict@v2.0.1 - uses: eps1lon/actions-label-merge-conflict@v2.0.1
with: with:
dirtyLabel: 'merge conflict' dirtyLabel: 'merge conflict'
repoToken: ${{ secrets.GH_TOKEN }} repoToken: ${{ secrets.JF_BOT_TOKEN }}

@ -11,17 +11,17 @@ jobs:
- name: Notify as seen - name: Notify as seen
uses: peter-evans/create-or-update-comment@v1.4.5 uses: peter-evans/create-or-update-comment@v1.4.5
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
comment-id: ${{ github.event.comment.id }} comment-id: ${{ github.event.comment.id }}
reactions: '+1' reactions: '+1'
- name: Checkout the latest code - name: Checkout the latest code
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
token: ${{ secrets.GH_TOKEN }} token: ${{ secrets.JF_BOT_TOKEN }}
fetch-depth: 0 fetch-depth: 0
- name: Automatic Rebase - name: Automatic Rebase
uses: cirrus-actions/rebase@1.4 uses: cirrus-actions/rebase@1.4
env: env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}

@ -1,7 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Audio namespace Emby.Naming.Audio
{ {
@ -18,8 +18,8 @@ namespace Emby.Naming.Audio
/// <returns>True if file at path is audio file.</returns> /// <returns>True if file at path is audio file.</returns>
public static bool IsAudioFile(string path, NamingOptions options) public static bool IsAudioFile(string path, NamingOptions options)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return options.AudioFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
} }
} }

@ -23,11 +23,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\SharedVersion.cs" /> <Compile Include="../SharedVersion.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" /> <ProjectReference Include="../MediaBrowser.Common/MediaBrowser.Common.csproj" />
<ProjectReference Include="../MediaBrowser.Model/MediaBrowser.Model.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

@ -29,70 +29,73 @@ namespace Emby.Naming.Video
/// <param name="path">Path to file.</param> /// <param name="path">Path to file.</param>
/// <returns>Returns <see cref="ExtraResult"/> object.</returns> /// <returns>Returns <see cref="ExtraResult"/> object.</returns>
public ExtraResult GetExtraInfo(string path) public ExtraResult GetExtraInfo(string path)
{
return _options.VideoExtraRules
.Select(i => GetExtraInfo(path, i))
.FirstOrDefault(i => i.ExtraType != null) ?? new ExtraResult();
}
private ExtraResult GetExtraInfo(string path, ExtraRule rule)
{ {
var result = new ExtraResult(); var result = new ExtraResult();
if (rule.MediaType == MediaType.Audio) for (var i = 0; i < _options.VideoExtraRules.Length; i++)
{ {
if (!AudioFileParser.IsAudioFile(path, _options)) var rule = _options.VideoExtraRules[i];
if (rule.MediaType == MediaType.Audio)
{ {
return result; if (!AudioFileParser.IsAudioFile(path, _options))
{
continue;
}
} }
} else if (rule.MediaType == MediaType.Video)
else if (rule.MediaType == MediaType.Video)
{
if (!new VideoResolver(_options).IsVideoFile(path))
{ {
return result; if (!new VideoResolver(_options).IsVideoFile(path))
{
continue;
}
} }
}
if (rule.RuleType == ExtraRuleType.Filename)
{
var filename = Path.GetFileNameWithoutExtension(path);
if (string.Equals(filename, rule.Token, StringComparison.OrdinalIgnoreCase)) var pathSpan = path.AsSpan();
if (rule.RuleType == ExtraRuleType.Filename)
{ {
result.ExtraType = rule.ExtraType; var filename = Path.GetFileNameWithoutExtension(pathSpan);
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Suffix)
{
var filename = Path.GetFileNameWithoutExtension(path);
if (filename.IndexOf(rule.Token, StringComparison.OrdinalIgnoreCase) > 0) if (filename.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.Suffix)
{ {
result.ExtraType = rule.ExtraType; var filename = Path.GetFileNameWithoutExtension(pathSpan);
result.Rule = rule;
if (filename.Contains(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
} }
} else if (rule.RuleType == ExtraRuleType.Regex)
else if (rule.RuleType == ExtraRuleType.Regex) {
{ var filename = Path.GetFileName(path);
var filename = Path.GetFileName(path);
var regex = new Regex(rule.Token, RegexOptions.IgnoreCase); var regex = new Regex(rule.Token, RegexOptions.IgnoreCase);
if (regex.IsMatch(filename)) if (regex.IsMatch(filename))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
}
else if (rule.RuleType == ExtraRuleType.DirectoryName)
{ {
result.ExtraType = rule.ExtraType; var directoryName = Path.GetFileName(Path.GetDirectoryName(pathSpan));
result.Rule = rule; if (directoryName.Equals(rule.Token, StringComparison.OrdinalIgnoreCase))
{
result.ExtraType = rule.ExtraType;
result.Rule = rule;
}
} }
}
else if (rule.RuleType == ExtraRuleType.DirectoryName) if (result.ExtraType != null)
{
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
{ {
result.ExtraType = rule.ExtraType; return result;
result.Rule = rule;
} }
} }

@ -1,8 +1,8 @@
using System; using System;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq;
using Emby.Naming.Common; using Emby.Naming.Common;
using MediaBrowser.Common.Extensions;
namespace Emby.Naming.Video namespace Emby.Naming.Video
{ {
@ -59,15 +59,15 @@ namespace Emby.Naming.Video
} }
bool isStub = false; bool isStub = false;
string? container = null; ReadOnlySpan<char> container = ReadOnlySpan<char>.Empty;
string? stubType = null; string? stubType = null;
if (!isDirectory) if (!isDirectory)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
// Check supported extensions // Check supported extensions
if (!_options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) if (!_options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase))
{ {
// It's not supported. Check stub extensions // It's not supported. Check stub extensions
if (!StubResolver.TryResolveFile(path, _options, out stubType)) if (!StubResolver.TryResolveFile(path, _options, out stubType))
@ -86,9 +86,7 @@ namespace Emby.Naming.Video
var extraResult = new ExtraResolver(_options).GetExtraInfo(path); var extraResult = new ExtraResolver(_options).GetExtraInfo(path);
var name = isDirectory var name = Path.GetFileNameWithoutExtension(path);
? Path.GetFileName(path)
: Path.GetFileNameWithoutExtension(path);
int? year = null; int? year = null;
@ -107,7 +105,7 @@ namespace Emby.Naming.Video
return new VideoFileInfo( return new VideoFileInfo(
path: path, path: path,
container: container, container: container.IsEmpty ? null : container.ToString(),
isStub: isStub, isStub: isStub,
name: name, name: name,
year: year, year: year,
@ -126,8 +124,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file.</returns> /// <returns>True if is video file.</returns>
public bool IsVideoFile(string path) public bool IsVideoFile(string path)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return _options.VideoFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.VideoFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>
@ -137,8 +135,8 @@ namespace Emby.Naming.Video
/// <returns>True if is video file stub.</returns> /// <returns>True if is video file stub.</returns>
public bool IsStubFile(string path) public bool IsStubFile(string path)
{ {
var extension = Path.GetExtension(path); var extension = Path.GetExtension(path.AsSpan());
return _options.StubFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase); return _options.StubFileExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase);
} }
/// <summary> /// <summary>

@ -181,11 +181,9 @@ namespace Emby.Server.Implementations.Data
foreach (var row in connection.Query("PRAGMA table_info(" + table + ")")) foreach (var row in connection.Query("PRAGMA table_info(" + table + ")"))
{ {
if (row[1].SQLiteType != SQLiteType.Null) if (row.TryGetString(1, out var columnName))
{ {
var name = row[1].ToString(); columnNames.Add(columnName);
columnNames.Add(name);
} }
} }

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization; using System.Globalization;
using SQLitePCL.pretty; using SQLitePCL.pretty;
@ -96,21 +97,43 @@ namespace Emby.Server.Implementations.Data
DateTimeStyles.None).ToUniversalTime(); DateTimeStyles.None).ToUniversalTime();
} }
public static DateTime? TryReadDateTime(this IResultSetValue result) public static bool TryReadDateTime(this IReadOnlyList<IResultSetValue> reader, int index, out DateTime result)
{ {
var dateText = result.ToString(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
var dateText = item.ToString();
if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult)) if (DateTime.TryParseExact(dateText, _datetimeFormats, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None, out var dateTimeResult))
{ {
return dateTimeResult.ToUniversalTime(); result = dateTimeResult.ToUniversalTime();
return true;
}
result = default;
return false;
}
public static bool TryGetGuid(this IReadOnlyList<IResultSetValue> reader, int index, out Guid result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
} }
return null; result = item.ReadGuidFromBlob();
return true;
} }
public static bool IsDBNull(this IReadOnlyList<IResultSetValue> result, int index) public static bool IsDbNull(this IResultSetValue result)
{ {
return result[index].SQLiteType == SQLiteType.Null; return result.SQLiteType == SQLiteType.Null;
} }
public static string GetString(this IReadOnlyList<IResultSetValue> result, int index) public static string GetString(this IReadOnlyList<IResultSetValue> result, int index)
@ -118,14 +141,48 @@ namespace Emby.Server.Implementations.Data
return result[index].ToString(); return result[index].ToString();
} }
public static bool TryGetString(this IReadOnlyList<IResultSetValue> reader, int index, out string result)
{
result = null;
var item = reader[index];
if (item.IsDbNull())
{
return false;
}
result = item.ToString();
return true;
}
public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index) public static bool GetBoolean(this IReadOnlyList<IResultSetValue> result, int index)
{ {
return result[index].ToBool(); return result[index].ToBool();
} }
public static int GetInt32(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetBoolean(this IReadOnlyList<IResultSetValue> reader, int index, out bool result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToBool();
return true;
}
public static bool TryGetInt32(this IReadOnlyList<IResultSetValue> reader, int index, out int result)
{ {
return result[index].ToInt(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToInt();
return true;
} }
public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index) public static long GetInt64(this IReadOnlyList<IResultSetValue> result, int index)
@ -133,9 +190,43 @@ namespace Emby.Server.Implementations.Data
return result[index].ToInt64(); return result[index].ToInt64();
} }
public static float GetFloat(this IReadOnlyList<IResultSetValue> result, int index) public static bool TryGetInt64(this IReadOnlyList<IResultSetValue> reader, int index, out long result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToInt64();
return true;
}
public static bool TryGetSingle(this IReadOnlyList<IResultSetValue> reader, int index, out float result)
{
var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToFloat();
return true;
}
public static bool TryGetDouble(this IReadOnlyList<IResultSetValue> reader, int index, out double result)
{ {
return result[index].ToFloat(); var item = reader[index];
if (item.IsDbNull())
{
result = default;
return false;
}
result = item.ToDouble();
return true;
} }
public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index) public static Guid GetGuid(this IReadOnlyList<IResultSetValue> result, int index)

File diff suppressed because it is too large Load Diff

@ -355,9 +355,9 @@ namespace Emby.Server.Implementations.Data
userData.Key = reader[0].ToString(); userData.Key = reader[0].ToString();
// userData.UserId = reader[1].ReadGuidFromBlob(); // userData.UserId = reader[1].ReadGuidFromBlob();
if (reader[2].SQLiteType != SQLiteType.Null) if (reader.TryGetDouble(2, out var rating))
{ {
userData.Rating = reader[2].ToDouble(); userData.Rating = rating;
} }
userData.Played = reader[3].ToBool(); userData.Played = reader[3].ToBool();
@ -365,19 +365,19 @@ namespace Emby.Server.Implementations.Data
userData.IsFavorite = reader[5].ToBool(); userData.IsFavorite = reader[5].ToBool();
userData.PlaybackPositionTicks = reader[6].ToInt64(); userData.PlaybackPositionTicks = reader[6].ToInt64();
if (reader[7].SQLiteType != SQLiteType.Null) if (reader.TryReadDateTime(7, out var lastPlayedDate))
{ {
userData.LastPlayedDate = reader[7].TryReadDateTime(); userData.LastPlayedDate = lastPlayedDate;
} }
if (reader[8].SQLiteType != SQLiteType.Null) if (reader.TryGetInt32(8, out var audioStreamIndex))
{ {
userData.AudioStreamIndex = reader[8].ToInt(); userData.AudioStreamIndex = audioStreamIndex;
} }
if (reader[9].SQLiteType != SQLiteType.Null) if (reader.TryGetInt32(9, out var subtitleStreamIndex))
{ {
userData.SubtitleStreamIndex = reader[9].ToInt(); userData.SubtitleStreamIndex = subtitleStreamIndex;
} }
return userData; return userData;

@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Mono.Nat" Version="3.0.1" /> <PackageReference Include="Mono.Nat" Version="3.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.1.0" />
<PackageReference Include="sharpcompress" Version="0.28.2" /> <PackageReference Include="sharpcompress" Version="0.28.2" />

@ -2,8 +2,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net; using System.Net;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Security;
@ -215,7 +215,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Request.Headers[HeaderNames.Authorization]; auth = httpReq.Request.Headers[HeaderNames.Authorization];
} }
return GetAuthorization(auth); return GetAuthorization(auth.Count > 0 ? auth[0] : null);
} }
/// <summary> /// <summary>
@ -232,7 +232,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
auth = httpReq.Headers[HeaderNames.Authorization]; auth = httpReq.Headers[HeaderNames.Authorization];
} }
return GetAuthorization(auth); return GetAuthorization(auth.Count > 0 ? auth[0] : null);
} }
/// <summary> /// <summary>
@ -240,43 +240,43 @@ namespace Emby.Server.Implementations.HttpServer.Security
/// </summary> /// </summary>
/// <param name="authorizationHeader">The authorization header.</param> /// <param name="authorizationHeader">The authorization header.</param>
/// <returns>Dictionary{System.StringSystem.String}.</returns> /// <returns>Dictionary{System.StringSystem.String}.</returns>
private Dictionary<string, string> GetAuthorization(string authorizationHeader) private Dictionary<string, string> GetAuthorization(ReadOnlySpan<char> authorizationHeader)
{ {
if (authorizationHeader == null) if (authorizationHeader == null)
{ {
return null; return null;
} }
var parts = authorizationHeader.Split(' ', 2); var firstSpace = authorizationHeader.IndexOf(' ');
// There should be at least to parts // There should be at least two parts
if (parts.Length != 2) if (firstSpace == -1)
{ {
return null; return null;
} }
var acceptedNames = new[] { "MediaBrowser", "Emby" }; var name = authorizationHeader[..firstSpace];
// It has to be a digest request if (!name.Equals("MediaBrowser", StringComparison.OrdinalIgnoreCase)
if (!acceptedNames.Contains(parts[0], StringComparer.OrdinalIgnoreCase)) && !name.Equals("Emby", StringComparison.OrdinalIgnoreCase))
{ {
return null; return null;
} }
// Remove uptil the first space authorizationHeader = authorizationHeader[(firstSpace + 1)..];
authorizationHeader = parts[1];
parts = authorizationHeader.Split(',');
var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in parts) foreach (var item in authorizationHeader.Split(','))
{ {
var param = item.Trim().Split('=', 2); var trimmedItem = item.Trim();
var firstEqualsSign = trimmedItem.IndexOf('=');
if (param.Length == 2) if (firstEqualsSign > 0)
{ {
var value = NormalizeValue(param[1].Trim('"')); var key = trimmedItem[..firstEqualsSign].ToString();
result[param[0]] = value; var value = NormalizeValue(trimmedItem[(firstEqualsSign + 1)..].Trim('"').ToString());
result[key] = value;
} }
} }

@ -165,13 +165,13 @@ namespace Emby.Server.Implementations.Library.Resolvers
protected void SetVideoType(Video video, VideoFileInfo videoInfo) protected void SetVideoType(Video video, VideoFileInfo videoInfo)
{ {
var extension = Path.GetExtension(video.Path); var extension = Path.GetExtension(video.Path.AsSpan());
video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) || video.VideoType = extension.Equals(".iso", StringComparison.OrdinalIgnoreCase)
string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ? || extension.Equals(".img", StringComparison.OrdinalIgnoreCase)
VideoType.Iso : ? VideoType.Iso
VideoType.VideoFile; : VideoType.VideoFile;
video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase); video.IsShortcut = extension.Equals(".strm", StringComparison.OrdinalIgnoreCase);
video.IsPlaceHolder = videoInfo.IsStub; video.IsPlaceHolder = videoInfo.IsStub;
if (videoInfo.IsStub) if (videoInfo.IsStub)
@ -193,11 +193,11 @@ namespace Emby.Server.Implementations.Library.Resolvers
{ {
if (video.VideoType == VideoType.Iso) if (video.VideoType == VideoType.Iso)
{ {
if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1) if (video.Path.Contains("dvd", StringComparison.OrdinalIgnoreCase))
{ {
video.IsoType = IsoType.Dvd; video.IsoType = IsoType.Dvd;
} }
else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1) else if (video.Path.Contains("bluray", StringComparison.OrdinalIgnoreCase))
{ {
video.IsoType = IsoType.BluRay; video.IsoType = IsoType.BluRay;
} }

@ -6,7 +6,7 @@ using MediaBrowser.Controller.LiveTv;
namespace Emby.Server.Implementations.LiveTv.EmbyTV namespace Emby.Server.Implementations.LiveTv.EmbyTV
{ {
internal class RecordingHelper internal static class RecordingHelper
{ {
public static DateTime GetStartTime(TimerInfo timer) public static DateTime GetStartTime(TimerInfo timer)
{ {
@ -70,17 +70,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private static string GetDateString(DateTime date) private static string GetDateString(DateTime date)
{ {
date = date.ToLocalTime(); return date.ToLocalTime().ToString("yyyy_MM_dd_HH_mm_ss", CultureInfo.InvariantCulture);
return string.Format(
CultureInfo.InvariantCulture,
"{0}_{1}_{2}_{3}_{4}_{5}",
date.Year.ToString("0000", CultureInfo.InvariantCulture),
date.Month.ToString("00", CultureInfo.InvariantCulture),
date.Day.ToString("00", CultureInfo.InvariantCulture),
date.Hour.ToString("00", CultureInfo.InvariantCulture),
date.Minute.ToString("00", CultureInfo.InvariantCulture),
date.Second.ToString("00", CultureInfo.InvariantCulture));
} }
} }
} }

@ -74,7 +74,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{ {
var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false); var model = await GetModelInfo(info, false, cancellationToken).ConfigureAwait(false);
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(model.LineupURL ?? model.BaseURL + "/lineup.json", HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false); await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken) var lineup = await JsonSerializer.DeserializeAsync<List<Channels>>(stream, _jsonOptions, cancellationToken)
.ConfigureAwait(false) ?? new List<Channels>(); .ConfigureAwait(false) ?? new List<Channels>();

@ -39,7 +39,7 @@
"MixedContent": "Смесено съдържание", "MixedContent": "Смесено съдържание",
"Movies": "Филми", "Movies": "Филми",
"Music": "Музика", "Music": "Музика",
"MusicVideos": "Музикални клипове", "MusicVideos": "Музикални видеа",
"NameInstallFailed": "{0} не можа да се инсталира", "NameInstallFailed": "{0} не можа да се инсталира",
"NameSeasonNumber": "Сезон {0}", "NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Неразпознат сезон", "NameSeasonUnknown": "Неразпознат сезон",
@ -62,7 +62,7 @@
"NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно", "NotificationOptionVideoPlaybackStopped": "Възпроизвеждането на видео е спряно",
"Photos": "Снимки", "Photos": "Снимки",
"Playlists": "Списъци", "Playlists": "Списъци",
"Plugin": "Приставка", "Plugin": "Добавка",
"PluginInstalledWithName": "{0} е инсталиранa", "PluginInstalledWithName": "{0} е инсталиранa",
"PluginUninstalledWithName": "{0} е деинсталиранa", "PluginUninstalledWithName": "{0} е деинсталиранa",
"PluginUpdatedWithName": "{0} е обновенa", "PluginUpdatedWithName": "{0} е обновенa",
@ -116,5 +116,7 @@
"TasksMaintenanceCategory": "Поддръжка", "TasksMaintenanceCategory": "Поддръжка",
"Undefined": "Неопределено", "Undefined": "Неопределено",
"Forced": "Принудително", "Forced": "Принудително",
"Default": "По подразбиране" "Default": "По подразбиране",
"TaskCleanActivityLogDescription": "Изтрива записите в дневника с активност по стари от конфигурираната възраст.",
"TaskCleanActivityLog": "Изчисти дневника с активност"
} }

@ -1,7 +1,7 @@
{ {
"DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে", "DeviceOnlineWithName": "{0}-এর সাথে সংযুক্ত হয়েছে",
"DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে", "DeviceOfflineWithName": "{0}-এর সাথে সংযোগ বিচ্ছিন্ন হয়েছে",
"Collections": "কলেক্শন", "Collections": "সংগ্রহ",
"ChapterNameValue": "অধ্যায় {0}", "ChapterNameValue": "অধ্যায় {0}",
"Channels": "চ্যানেল", "Channels": "চ্যানেল",
"CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে", "CameraImageUploadedFrom": "{0} থেকে একটি নতুন ক্যামেরার চিত্র আপলোড করা হয়েছে",

@ -59,7 +59,7 @@
"NotificationOptionTaskFailed": "Scheduled task failure", "NotificationOptionTaskFailed": "Scheduled task failure",
"NotificationOptionUserLockedOut": "User locked out", "NotificationOptionUserLockedOut": "User locked out",
"NotificationOptionVideoPlayback": "Video playback started", "NotificationOptionVideoPlayback": "Video playback started",
"NotificationOptionVideoPlaybackStopped": "Video playback stopped", "NotificationOptionVideoPlaybackStopped": "Ulangmain video dihentikan",
"Photos": "Gambar-gambar", "Photos": "Gambar-gambar",
"Playlists": "Senarai main", "Playlists": "Senarai main",
"Plugin": "Plugin", "Plugin": "Plugin",
@ -72,9 +72,9 @@
"ServerNameNeedsToBeRestarted": "{0} needs to be restarted", "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
"Shows": "Series", "Shows": "Series",
"Songs": "Lagu-lagu", "Songs": "Lagu-lagu",
"StartupEmbyServerIsLoading": "Jellyfin Server sedang dimuatkan. Sila cuba sebentar lagi.", "StartupEmbyServerIsLoading": "Pelayan Jellyfin sedang dimuatkan. Sila cuba sebentar lagi.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}", "SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
"SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}", "SubtitleDownloadFailureFromForItem": "Muat turun sarikata gagal dari {0} untuk {1}",
"Sync": "Sync", "Sync": "Sync",
"System": "Sistem", "System": "Sistem",
"TvShows": "TV Shows", "TvShows": "TV Shows",

@ -19,7 +19,7 @@
"HeaderFavoriteArtists": "Favorittartistar", "HeaderFavoriteArtists": "Favorittartistar",
"HeaderFavoriteAlbums": "Favorittalbum", "HeaderFavoriteAlbums": "Favorittalbum",
"HeaderContinueWatching": "Fortsett å sjå", "HeaderContinueWatching": "Fortsett å sjå",
"HeaderAlbumArtists": "Albumartistar", "HeaderAlbumArtists": "Albumartist",
"Genres": "Sjangrar", "Genres": "Sjangrar",
"Folders": "Mapper", "Folders": "Mapper",
"Favorites": "Favorittar", "Favorites": "Favorittar",
@ -97,8 +97,8 @@
"System": "System", "System": "System",
"SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}", "SubtitleDownloadFailureFromForItem": "Feila å laste ned undertekstar frå {0} for {1}",
"StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.", "StartupEmbyServerIsLoading": "Jellyfin-tenaren laster. Prøv igjen seinare.",
"Songs": "Songar", "Songs": "Sangar",
"Shows": "Program", "Shows": "Seriar",
"ServerNameNeedsToBeRestarted": "{0} må omstartast", "ServerNameNeedsToBeRestarted": "{0} må omstartast",
"ScheduledTaskStartedWithName": "{0} starta", "ScheduledTaskStartedWithName": "{0} starta",
"ScheduledTaskFailedWithName": "{0} feila", "ScheduledTaskFailedWithName": "{0} feila",
@ -107,7 +107,7 @@
"PluginUninstalledWithName": "{0} blei avinstallert", "PluginUninstalledWithName": "{0} blei avinstallert",
"PluginInstalledWithName": "{0} blei installert", "PluginInstalledWithName": "{0} blei installert",
"Plugin": "Programvaretillegg", "Plugin": "Programvaretillegg",
"Playlists": "Speleliste", "Playlists": "Spelelister",
"Photos": "Bilete", "Photos": "Bilete",
"NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa", "NotificationOptionVideoPlaybackStopped": "Videoavspeling stoppa",
"NotificationOptionVideoPlayback": "Videoavspeling starta", "NotificationOptionVideoPlayback": "Videoavspeling starta",

@ -297,50 +297,49 @@ namespace Emby.Server.Implementations.Security
AccessToken = reader[1].ToString() AccessToken = reader[1].ToString()
}; };
if (reader[2].SQLiteType != SQLiteType.Null) if (reader.TryGetString(2, out var deviceId))
{ {
info.DeviceId = reader[2].ToString(); info.DeviceId = deviceId;
} }
if (reader[3].SQLiteType != SQLiteType.Null) if (reader.TryGetString(3, out var appName))
{ {
info.AppName = reader[3].ToString(); info.AppName = appName;
} }
if (reader[4].SQLiteType != SQLiteType.Null) if (reader.TryGetString(4, out var appVersion))
{ {
info.AppVersion = reader[4].ToString(); info.AppVersion = appVersion;
} }
if (reader[5].SQLiteType != SQLiteType.Null) if (reader.TryGetString(6, out var userId))
{ {
info.DeviceName = reader[5].ToString(); info.UserId = new Guid(userId);
} }
if (reader[6].SQLiteType != SQLiteType.Null) if (reader.TryGetString(7, out var userName))
{ {
info.UserId = new Guid(reader[6].ToString()); info.UserName = userName;
}
if (reader[7].SQLiteType != SQLiteType.Null)
{
info.UserName = reader[7].ToString();
} }
info.DateCreated = reader[8].ReadDateTime(); info.DateCreated = reader[8].ReadDateTime();
if (reader[9].SQLiteType != SQLiteType.Null) if (reader.TryReadDateTime(9, out var dateLastActivity))
{ {
info.DateLastActivity = reader[9].ReadDateTime(); info.DateLastActivity = dateLastActivity;
} }
else else
{ {
info.DateLastActivity = info.DateCreated; info.DateLastActivity = info.DateCreated;
} }
if (reader[10].SQLiteType != SQLiteType.Null) if (reader.TryGetString(10, out var customName))
{
info.DeviceName = customName;
}
else if (reader.TryGetString(5, out var deviceName))
{ {
info.DeviceName = reader[10].ToString(); info.DeviceName = deviceName;
} }
return info; return info;
@ -361,9 +360,9 @@ namespace Emby.Server.Implementations.Security
foreach (var row in statement.ExecuteQuery()) foreach (var row in statement.ExecuteQuery())
{ {
if (row[0].SQLiteType != SQLiteType.Null) if (row.TryGetString(0, out var customName))
{ {
result.CustomName = row[0].ToString(); result.CustomName = customName;
} }
} }

@ -77,6 +77,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param> /// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Total record count.</param> /// <param name="enableTotalRecordCount">Total record count.</param>
/// <response code="200">Artists returned.</response> /// <response code="200">Artists returned.</response>
@ -112,6 +114,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith, [FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan, [FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true, [FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true) [FromQuery] bool enableTotalRecordCount = true)
{ {
@ -150,7 +154,8 @@ namespace Jellyfin.Api.Controllers
MinCommunityRating = minCommunityRating, MinCommunityRating = minCommunityRating,
DtoOptions = dtoOptions, DtoOptions = dtoOptions,
SearchTerm = searchTerm, SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount EnableTotalRecordCount = enableTotalRecordCount,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
}; };
if (parentId.HasValue) if (parentId.HasValue)

@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param> /// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param> /// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Genres returned.</response> /// <response code="200">Genres returned.</response>
@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith, [FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan, [FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true, [FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true) [FromQuery] bool enableTotalRecordCount = true)
{ {
@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater, NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions, DtoOptions = dtoOptions,
SearchTerm = searchTerm, SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount EnableTotalRecordCount = enableTotalRecordCount,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
}; };
if (parentId.HasValue) if (parentId.HasValue)

@ -63,6 +63,8 @@ namespace Jellyfin.Api.Controllers
/// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param> /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a given input string.</param>
/// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param> /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</param>
/// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param> /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited.</param>
/// <param name="sortOrder">Sort Order - Ascending,Descending.</param>
/// <param name="enableImages">Optional, include image information in output.</param> /// <param name="enableImages">Optional, include image information in output.</param>
/// <param name="enableTotalRecordCount">Optional. Include total record count.</param> /// <param name="enableTotalRecordCount">Optional. Include total record count.</param>
/// <response code="200">Music genres returned.</response> /// <response code="200">Music genres returned.</response>
@ -84,6 +86,8 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? nameStartsWithOrGreater, [FromQuery] string? nameStartsWithOrGreater,
[FromQuery] string? nameStartsWith, [FromQuery] string? nameStartsWith,
[FromQuery] string? nameLessThan, [FromQuery] string? nameLessThan,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] SortOrder[] sortOrder,
[FromQuery] bool? enableImages = true, [FromQuery] bool? enableImages = true,
[FromQuery] bool enableTotalRecordCount = true) [FromQuery] bool enableTotalRecordCount = true)
{ {
@ -107,7 +111,8 @@ namespace Jellyfin.Api.Controllers
NameStartsWithOrGreater = nameStartsWithOrGreater, NameStartsWithOrGreater = nameStartsWithOrGreater,
DtoOptions = dtoOptions, DtoOptions = dtoOptions,
SearchTerm = searchTerm, SearchTerm = searchTerm,
EnableTotalRecordCount = enableTotalRecordCount EnableTotalRecordCount = enableTotalRecordCount,
OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder)
}; };
if (parentId.HasValue) if (parentId.HasValue)

@ -15,7 +15,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.5" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.6" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.1.4" />

@ -27,13 +27,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" /> <PackageReference Include="System.Linq.Async" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.5" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.6">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.5"> <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.6">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

@ -38,8 +38,8 @@
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.5" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="5.0.6" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.5" /> <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="5.0.6" />
<PackageReference Include="prometheus-net" Version="4.1.1" /> <PackageReference Include="prometheus-net" Version="4.1.1" />
<PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" /> <PackageReference Include="prometheus-net.AspNetCore" Version="4.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" /> <PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />

@ -81,7 +81,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Tests", "te
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Server.Integration.Tests", "tests\Jellyfin.Server.Integration.Tests\Jellyfin.Server.Integration.Tests.csproj", "{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{CE21845F-74AD-42F6-B7BA-90506E6CD09E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Providers.Tests", "tests\Jellyfin.Providers.Tests\Jellyfin.Providers.Tests.csproj", "{A964008C-2136-4716-B6CB-B3426C22320A}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -225,10 +225,10 @@ Global
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E}.Release|Any CPU.Build.0 = Release|Any CPU
{CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Debug|Any CPU.Build.0 = Debug|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Release|Any CPU.ActiveCfg = Release|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CE21845F-74AD-42F6-B7BA-90506E6CD09E}.Release|Any CPU.Build.0 = Release|Any CPU {A964008C-2136-4716-B6CB-B3426C22320A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -246,7 +246,7 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {68B0B823-A5AC-4E8B-82EA-965AAC7BF76E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{CE21845F-74AD-42F6-B7BA-90506E6CD09E} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {A964008C-2136-4716-B6CB-B3426C22320A} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE} SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
/// Static extensions for the <see cref="IEnumerable{T}"/> interface.
/// </summary>
public static class EnumerableExtensions
{
/// <summary>
/// Determines whether the value is contained in the source collection.
/// </summary>
/// <param name="source">An instance of the <see cref="IEnumerable{String}"/> interface.</param>
/// <param name="value">The value to look for in the collection.</param>
/// <param name="stringComparison">The string comparison.</param>
/// <returns>A value indicating whether the value is contained in the collection.</returns>
/// <exception cref="ArgumentNullException">The source is null.</exception>
public static bool Contains(this IEnumerable<string> source, ReadOnlySpan<char> value, StringComparison stringComparison)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
if (source is IList<string> list)
{
int len = list.Count;
for (int i = 0; i < len; i++)
{
if (value.Equals(list[i], stringComparison))
{
return true;
}
}
return false;
}
foreach (string element in source)
{
if (value.Equals(element, stringComparison))
{
return true;
}
}
return false;
}
}
}

@ -84,7 +84,7 @@ namespace MediaBrowser.Controller.Channels
internal static bool IsChannelVisible(BaseItem channelItem, User user) internal static bool IsChannelVisible(BaseItem channelItem, User user)
{ {
var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString("")); var channel = ChannelManager.GetChannel(channelItem.ChannelId.ToString(string.Empty));
return channel.IsVisible(user); return channel.IsVisible(user);
} }

@ -51,32 +51,47 @@ namespace MediaBrowser.Controller.Channels
/// Gets the channels internal. /// Gets the channels internal.
/// </summary> /// </summary>
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>The channels.</returns>
QueryResult<Channel> GetChannelsInternal(ChannelQuery query); QueryResult<Channel> GetChannelsInternal(ChannelQuery query);
/// <summary> /// <summary>
/// Gets the channels. /// Gets the channels.
/// </summary> /// </summary>
/// <param name="query">The query.</param> /// <param name="query">The query.</param>
/// <returns>The channels.</returns>
QueryResult<BaseItemDto> GetChannels(ChannelQuery query); QueryResult<BaseItemDto> GetChannels(ChannelQuery query);
/// <summary> /// <summary>
/// Gets the latest media. /// Gets the latest channel items.
/// </summary> /// </summary>
/// <param name="query">The item query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The latest channels.</returns>
Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); Task<QueryResult<BaseItemDto>> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the latest media. /// Gets the latest channel items.
/// </summary> /// </summary>
/// <param name="query">The item query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The latest channels.</returns>
Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken); Task<QueryResult<BaseItem>> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the channel items. /// Gets the channel items.
/// </summary> /// </summary>
/// <param name="query">The query.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The channel items.</returns>
Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken); Task<QueryResult<BaseItemDto>> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken);
/// <summary> /// <summary>
/// Gets the channel items internal. /// Gets the channel items.
/// </summary> /// </summary>
/// <param name="query">The query.</param>
/// <param name="progress">The progress to report to.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The channel items.</returns>
Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken); Task<QueryResult<BaseItem>> GetChannelItemsInternal(InternalItemsQuery query, IProgress<double> progress, CancellationToken cancellationToken);
/// <summary> /// <summary>
@ -84,9 +99,14 @@ namespace MediaBrowser.Controller.Channels
/// </summary> /// </summary>
/// <param name="item">The item.</param> /// <param name="item">The item.</param>
/// <param name="cancellationToken">The cancellation token.</param> /// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{MediaSourceInfo}}.</returns> /// <returns>The item media sources.</returns>
IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken); IEnumerable<MediaSourceInfo> GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken);
/// <summary>
/// Whether the item supports media probe.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>Whether media probe should be enabled.</returns>
bool EnableMediaProbe(BaseItem item); bool EnableMediaProbe(BaseItem item);
} }
} }

@ -0,0 +1,8 @@
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Channels
{
public interface IDisableMediaSourceDisplay
{
}
}

@ -0,0 +1,9 @@
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Channels
{
public interface IHasFolderAttributes
{
string[] Attributes { get; }
}
}

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -7,11 +5,17 @@ using MediaBrowser.Model.Dto;
namespace MediaBrowser.Controller.Channels namespace MediaBrowser.Controller.Channels
{ {
/// <summary>
/// The channel requires a media info callback.
/// </summary>
public interface IRequiresMediaInfoCallback public interface IRequiresMediaInfoCallback
{ {
/// <summary> /// <summary>
/// Gets the channel item media information. /// Gets the channel item media information.
/// </summary> /// </summary>
/// <param name="id">The channel item id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The enumerable of media source info.</returns>
Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken); Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaInfo(string id, CancellationToken cancellationToken);
} }
} }

@ -5,7 +5,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Channels namespace MediaBrowser.Controller.Channels
{ {
@ -19,35 +18,4 @@ namespace MediaBrowser.Controller.Channels
/// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns> /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken); Task<IEnumerable<ChannelItemInfo>> Search(ChannelSearchInfo searchInfo, CancellationToken cancellationToken);
} }
public interface ISupportsLatestMedia
{
/// <summary>
/// Gets the latest media.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns>
Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken);
}
public interface ISupportsDelete
{
bool CanDelete(BaseItem item);
Task DeleteItem(string id, CancellationToken cancellationToken);
}
public interface IDisableMediaSourceDisplay
{
}
public interface ISupportsMediaProbe
{
}
public interface IHasFolderAttributes
{
string[] Attributes { get; }
}
} }

@ -0,0 +1,15 @@
#pragma warning disable CS1591
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
namespace MediaBrowser.Controller.Channels
{
public interface ISupportsDelete
{
bool CanDelete(BaseItem item);
Task DeleteItem(string id, CancellationToken cancellationToken);
}
}

@ -0,0 +1,21 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Channels
{
public interface ISupportsLatestMedia
{
/// <summary>
/// Gets the latest media.
/// </summary>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The latest media.</returns>
Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken);
}
}

@ -0,0 +1,8 @@
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Channels
{
public interface ISupportsMediaProbe
{
}
}

@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Channels
public List<ChannelMediaContentType> ContentTypes { get; set; } public List<ChannelMediaContentType> ContentTypes { get; set; }
/// <summary> /// <summary>
/// Represents the maximum number of records the channel allows retrieving at a time. /// Gets or sets the maximum number of records the channel allows retrieving at a time.
/// </summary> /// </summary>
public int? MaxPageSize { get; set; } public int? MaxPageSize { get; set; }
@ -41,7 +41,7 @@ namespace MediaBrowser.Controller.Channels
public List<ChannelItemSortField> DefaultSortFields { get; set; } public List<ChannelItemSortField> DefaultSortFields { get; set; }
/// <summary> /// <summary>
/// Indicates if a sort ascending/descending toggle is supported or not. /// Gets or sets a value indicating whether a sort ascending/descending toggle is supported or not.
/// </summary> /// </summary>
public bool SupportsSortOrderToggle { get; set; } public bool SupportsSortOrderToggle { get; set; }

@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Devices
/// </summary> /// </summary>
/// <param name="reportedId">The reported identifier.</param> /// <param name="reportedId">The reported identifier.</param>
/// <param name="capabilities">The capabilities.</param> /// <param name="capabilities">The capabilities.</param>
/// <returns>Task.</returns>
void SaveCapabilities(string reportedId, ClientCapabilities capabilities); void SaveCapabilities(string reportedId, ClientCapabilities capabilities);
/// <summary> /// <summary>
@ -47,6 +46,9 @@ namespace MediaBrowser.Controller.Devices
/// <summary> /// <summary>
/// Determines whether this instance [can access device] the specified user identifier. /// Determines whether this instance [can access device] the specified user identifier.
/// </summary> /// </summary>
/// <param name="user">The user to test.</param>
/// <param name="deviceId">The device id to test.</param>
/// <returns>Whether the user can access the device.</returns>
bool CanAccessDevice(User user, string deviceId); bool CanAccessDevice(User user, string deviceId);
void UpdateDeviceOptions(string deviceId, DeviceOptions options); void UpdateDeviceOptions(string deviceId, DeviceOptions options);

@ -36,11 +36,17 @@ namespace MediaBrowser.Controller.Dto
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="user">The user.</param> /// <param name="user">The user.</param>
/// <param name="owner">The owner.</param> /// <param name="owner">The owner.</param>
/// <returns>The <see cref="IReadOnlyList{T}"/> of <see cref="BaseItemDto"/>.</returns>
IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null); IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
/// <summary> /// <summary>
/// Gets the item by name dto. /// Gets the item by name dto.
/// </summary> /// </summary>
/// <param name="item">The item.</param>
/// <param name="options">The dto options.</param>
/// <param name="taggedItems">The list of tagged items.</param>
/// <param name="user">The user.</param>
/// <returns>The item dto.</returns>
BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null); BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
} }
} }

@ -22,6 +22,8 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class AggregateFolder : Folder public class AggregateFolder : Folder
{ {
private bool _requiresRefresh;
public AggregateFolder() public AggregateFolder()
{ {
PhysicalLocationsList = Array.Empty<string>(); PhysicalLocationsList = Array.Empty<string>();
@ -85,8 +87,6 @@ namespace MediaBrowser.Controller.Entities
} }
} }
private bool _requiresRefresh;
public override bool RequiresRefresh() public override bool RequiresRefresh()
{ {
var changed = base.RequiresRefresh() || _requiresRefresh; var changed = base.RequiresRefresh() || _requiresRefresh;
@ -106,11 +106,11 @@ namespace MediaBrowser.Controller.Entities
return changed; return changed;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
ClearCache(); ClearCache();
var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
_requiresRefresh = false; _requiresRefresh = false;
return changed; return changed;
} }

@ -208,9 +208,9 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (IsAccessedByName) if (IsAccessedByName)
{ {

@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities.Audio
public override bool IsDisplayedAsFolder => true; public override bool IsDisplayedAsFolder => true;
/// <summary> /// <summary>
/// Returns the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
/// </summary> /// </summary>
/// <value>The containing folder path.</value> /// <value>The containing folder path.</value>
@ -106,9 +106,9 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
var newPath = GetRebasedPath(); var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal)) if (!string.Equals(Path, newPath, StringComparison.Ordinal))

@ -92,7 +92,8 @@ namespace MediaBrowser.Controller.Entities
public const string ShortsFolderName = "shorts"; public const string ShortsFolderName = "shorts";
public const string FeaturettesFolderName = "featurettes"; public const string FeaturettesFolderName = "featurettes";
public static readonly string[] AllExtrasTypesFolderNames = { public static readonly string[] AllExtrasTypesFolderNames =
{
ExtrasFolderName, ExtrasFolderName,
BehindTheScenesFolderName, BehindTheScenesFolderName,
DeletedScenesFolderName, DeletedScenesFolderName,
@ -177,7 +178,7 @@ namespace MediaBrowser.Controller.Entities
public virtual bool AlwaysScanInternalMetadataPath => false; public virtual bool AlwaysScanInternalMetadataPath => false;
/// <summary> /// <summary>
/// Gets a value indicating whether this instance is in mixed folder. /// Gets or sets a value indicating whether this instance is in mixed folder.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
[JsonIgnore] [JsonIgnore]
@ -244,7 +245,7 @@ namespace MediaBrowser.Controller.Entities
public ProgramAudio? Audio { get; set; } public ProgramAudio? Audio { get; set; }
/// <summary> /// <summary>
/// Return the id that should be used to key display prefs for this item. /// Gets the id that should be used to key display prefs for this item.
/// Default is based on the type for everything except actual generic folders. /// Default is based on the type for everything except actual generic folders.
/// </summary> /// </summary>
/// <value>The display prefs id.</value> /// <value>The display prefs id.</value>
@ -280,7 +281,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Returns the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
@ -305,8 +306,11 @@ namespace MediaBrowser.Controller.Entities
public string ServiceName { get; set; } public string ServiceName { get; set; }
/// <summary> /// <summary>
/// If this content came from an external service, the id of the content on that service. /// Gets or sets the external id.
/// </summary> /// </summary>
/// <remarks>
/// If this content came from an external service, the id of the content on that service.
/// </remarks>
[JsonIgnore] [JsonIgnore]
public string ExternalId { get; set; } public string ExternalId { get; set; }
@ -330,7 +334,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Gets or sets the type of the location. /// Gets the type of the location.
/// </summary> /// </summary>
/// <value>The type of the location.</value> /// <value>The type of the location.</value>
[JsonIgnore] [JsonIgnore]
@ -449,8 +453,11 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// This is just a helper for convenience. /// Gets the primary image path.
/// </summary> /// </summary>
/// <remarks>
/// This is just a helper for convenience.
/// </remarks>
/// <value>The primary image path.</value> /// <value>The primary image path.</value>
[JsonIgnore] [JsonIgnore]
public string PrimaryImagePath => this.GetImagePath(ImageType.Primary); public string PrimaryImagePath => this.GetImagePath(ImageType.Primary);
@ -541,7 +548,7 @@ namespace MediaBrowser.Controller.Entities
public DateTime DateLastRefreshed { get; set; } public DateTime DateLastRefreshed { get; set; }
/// <summary> /// <summary>
/// The logger. /// Gets or sets the logger.
/// </summary> /// </summary>
public static ILogger<BaseItem> Logger { get; set; } public static ILogger<BaseItem> Logger { get; set; }
@ -621,7 +628,7 @@ namespace MediaBrowser.Controller.Entities
private Guid[] _themeVideoIds; private Guid[] _themeVideoIds;
/// <summary> /// <summary>
/// Gets the name of the sort. /// Gets or sets the name of the sort.
/// </summary> /// </summary>
/// <value>The name of the sort.</value> /// <value>The name of the sort.</value>
[JsonIgnore] [JsonIgnore]
@ -848,7 +855,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// When the item first debuted. For movies this could be premiere date, episodes would be first aired /// Gets or sets the date that the item first debuted. For movies this could be premiere date, episodes would be first aired.
/// </summary> /// </summary>
/// <value>The premiere date.</value> /// <value>The premiere date.</value>
[JsonIgnore] [JsonIgnore]
@ -945,7 +952,7 @@ namespace MediaBrowser.Controller.Entities
public int? ProductionYear { get; set; } public int? ProductionYear { get; set; }
/// <summary> /// <summary>
/// If the item is part of a series, this is it's number in the series. /// Gets or sets the index number. If the item is part of a series, this is it's number in the series.
/// This could be episode number, album track number, etc. /// This could be episode number, album track number, etc.
/// </summary> /// </summary>
/// <value>The index number.</value> /// <value>The index number.</value>
@ -953,7 +960,7 @@ namespace MediaBrowser.Controller.Entities
public int? IndexNumber { get; set; } public int? IndexNumber { get; set; }
/// <summary> /// <summary>
/// For an episode this could be the season number, or for a song this could be the disc number. /// Gets or sets the parent index number. For an episode this could be the season number, or for a song this could be the disc number.
/// </summary> /// </summary>
/// <value>The parent index number.</value> /// <value>The parent index number.</value>
[JsonIgnore] [JsonIgnore]
@ -1017,9 +1024,9 @@ namespace MediaBrowser.Controller.Entities
} }
// if (!user.IsParentalScheduleAllowed()) // if (!user.IsParentalScheduleAllowed())
//{ // {
// return PlayAccess.None; // return PlayAccess.None;
//} // }
return PlayAccess.Full; return PlayAccess.Full;
} }
@ -2645,7 +2652,9 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true if changes were made. /// This is called before any metadata refresh and returns true if changes were made.
/// </summary> /// </summary>
public virtual bool BeforeMetadataRefresh(bool replaceAllMetdata) /// <param name="replaceAllMetadata">Whether to replace all metadata.</param>
/// <returns>true if the item has change, else false.</returns>
public virtual bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
_sortName = null; _sortName = null;

@ -29,30 +29,45 @@ namespace MediaBrowser.Controller.Entities
public class CollectionFolder : Folder, ICollectionFolder public class CollectionFolder : Folder, ICollectionFolder
{ {
private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options; private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
public static IXmlSerializer XmlSerializer { get; set; } private static readonly Dictionary<string, LibraryOptions> _libraryOptions = new Dictionary<string, LibraryOptions>();
private bool _requiresRefresh;
public static IServerApplicationHost ApplicationHost { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CollectionFolder"/> class.
/// </summary>
public CollectionFolder() public CollectionFolder()
{ {
PhysicalLocationsList = Array.Empty<string>(); PhysicalLocationsList = Array.Empty<string>();
PhysicalFolderIds = Array.Empty<Guid>(); PhysicalFolderIds = Array.Empty<Guid>();
} }
public static IXmlSerializer XmlSerializer { get; set; }
public static IServerApplicationHost ApplicationHost { get; set; }
[JsonIgnore] [JsonIgnore]
public override bool SupportsPlayedStatus => false; public override bool SupportsPlayedStatus => false;
[JsonIgnore] [JsonIgnore]
public override bool SupportsInheritedParentImages => false; public override bool SupportsInheritedParentImages => false;
public string CollectionType { get; set; }
/// <summary>
/// Gets the item's children.
/// </summary>
/// <remarks>
/// Our children are actually just references to the ones in the physical root...
/// </remarks>
/// <value>The actual children.</value>
[JsonIgnore]
public override IEnumerable<BaseItem> Children => GetActualChildren();
public override bool CanDelete() public override bool CanDelete()
{ {
return false; return false;
} }
public string CollectionType { get; set; }
private static readonly Dictionary<string, LibraryOptions> LibraryOptions = new Dictionary<string, LibraryOptions>();
public LibraryOptions GetLibraryOptions() public LibraryOptions GetLibraryOptions()
{ {
return GetLibraryOptions(Path); return GetLibraryOptions(Path);
@ -106,12 +121,12 @@ namespace MediaBrowser.Controller.Entities
public static LibraryOptions GetLibraryOptions(string path) public static LibraryOptions GetLibraryOptions(string path)
{ {
lock (LibraryOptions) lock (_libraryOptions)
{ {
if (!LibraryOptions.TryGetValue(path, out var options)) if (!_libraryOptions.TryGetValue(path, out var options))
{ {
options = LoadLibraryOptions(path); options = LoadLibraryOptions(path);
LibraryOptions[path] = options; _libraryOptions[path] = options;
} }
return options; return options;
@ -120,9 +135,9 @@ namespace MediaBrowser.Controller.Entities
public static void SaveLibraryOptions(string path, LibraryOptions options) public static void SaveLibraryOptions(string path, LibraryOptions options)
{ {
lock (LibraryOptions) lock (_libraryOptions)
{ {
LibraryOptions[path] = options; _libraryOptions[path] = options;
var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions); var clone = JsonSerializer.Deserialize<LibraryOptions>(JsonSerializer.SerializeToUtf8Bytes(options, _jsonOptions), _jsonOptions);
foreach (var mediaPath in clone.PathInfos) foreach (var mediaPath in clone.PathInfos)
@ -139,15 +154,18 @@ namespace MediaBrowser.Controller.Entities
public static void OnCollectionFolderChange() public static void OnCollectionFolderChange()
{ {
lock (LibraryOptions) lock (_libraryOptions)
{ {
LibraryOptions.Clear(); _libraryOptions.Clear();
} }
} }
/// <summary> /// <summary>
/// Allow different display preferences for each collection folder. /// Gets the display preferences id.
/// </summary> /// </summary>
/// <remarks>
/// Allow different display preferences for each collection folder.
/// </remarks>
/// <value>The display prefs id.</value> /// <value>The display prefs id.</value>
[JsonIgnore] [JsonIgnore]
public override Guid DisplayPreferencesId => Id; public override Guid DisplayPreferencesId => Id;
@ -155,21 +173,20 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public override string[] PhysicalLocations => PhysicalLocationsList; public override string[] PhysicalLocations => PhysicalLocationsList;
public string[] PhysicalLocationsList { get; set; }
public Guid[] PhysicalFolderIds { get; set; }
public override bool IsSaveLocalMetadataEnabled() public override bool IsSaveLocalMetadataEnabled()
{ {
return true; return true;
} }
public string[] PhysicalLocationsList { get; set; }
public Guid[] PhysicalFolderIds { get; set; }
protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService) protected override FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
{ {
return CreateResolveArgs(directoryService, true).FileSystemChildren; return CreateResolveArgs(directoryService, true).FileSystemChildren;
} }
private bool _requiresRefresh;
public override bool RequiresRefresh() public override bool RequiresRefresh()
{ {
var changed = base.RequiresRefresh() || _requiresRefresh; var changed = base.RequiresRefresh() || _requiresRefresh;
@ -201,9 +218,9 @@ namespace MediaBrowser.Controller.Entities
return changed; return changed;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var changed = base.BeforeMetadataRefresh(replaceAllMetdata) || _requiresRefresh; var changed = base.BeforeMetadataRefresh(replaceAllMetadata) || _requiresRefresh;
_requiresRefresh = false; _requiresRefresh = false;
return changed; return changed;
} }
@ -312,13 +329,6 @@ namespace MediaBrowser.Controller.Entities
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Our children are actually just references to the ones in the physical root...
/// </summary>
/// <value>The actual children.</value>
[JsonIgnore]
public override IEnumerable<BaseItem> Children => GetActualChildren();
public IEnumerable<BaseItem> GetActualChildren() public IEnumerable<BaseItem> GetActualChildren()
{ {
return GetPhysicalFolders(true).SelectMany(c => c.Children); return GetPhysicalFolders(true).SelectMany(c => c.Children);

@ -37,6 +37,11 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class Folder : BaseItem public class Folder : BaseItem
{ {
public Folder()
{
LinkedChildren = Array.Empty<LinkedChild>();
}
public static IUserViewManager UserViewManager { get; set; } public static IUserViewManager UserViewManager { get; set; }
/// <summary> /// <summary>
@ -50,11 +55,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public DateTime? DateLastMediaAdded { get; set; } public DateTime? DateLastMediaAdded { get; set; }
public Folder()
{
LinkedChildren = Array.Empty<LinkedChild>();
}
[JsonIgnore] [JsonIgnore]
public override bool SupportsThemeMedia => true; public override bool SupportsThemeMedia => true;
@ -86,6 +86,85 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public virtual bool SupportsDateLastMediaAdded => false; public virtual bool SupportsDateLastMediaAdded => false;
[JsonIgnore]
public override string FileNameWithoutExtension
{
get
{
if (IsFileProtocol)
{
return System.IO.Path.GetFileName(Path);
}
return null;
}
}
/// <summary>
/// Gets the actual children.
/// </summary>
/// <value>The actual children.</value>
[JsonIgnore]
public virtual IEnumerable<BaseItem> Children => LoadChildren();
/// <summary>
/// Gets thread-safe access to all recursive children of this folder - without regard to user.
/// </summary>
/// <value>The recursive children.</value>
[JsonIgnore]
public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren();
[JsonIgnore]
protected virtual bool SupportsShortcutChildren => false;
protected virtual bool FilterLinkedChildrenPerUser => false;
[JsonIgnore]
protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren;
[JsonIgnore]
public virtual bool SupportsUserDataFromChildren
{
get
{
// These are just far too slow.
if (this is ICollectionFolder)
{
return false;
}
if (this is UserView)
{
return false;
}
if (this is UserRootFolder)
{
return false;
}
if (this is Channel)
{
return false;
}
if (SourceType != SourceType.Library)
{
return false;
}
if (this is IItemByName)
{
if (this is not IHasDualAccess hasDualAccess || hasDualAccess.IsAccessedByName)
{
return false;
}
}
return true;
}
}
public override bool CanDelete() public override bool CanDelete()
{ {
if (IsRoot) if (IsRoot)
@ -108,20 +187,6 @@ namespace MediaBrowser.Controller.Entities
return baseResult; return baseResult;
} }
[JsonIgnore]
public override string FileNameWithoutExtension
{
get
{
if (IsFileProtocol)
{
return System.IO.Path.GetFileName(Path);
}
return null;
}
}
protected override bool IsAllowTagFilterEnforced() protected override bool IsAllowTagFilterEnforced()
{ {
if (this is ICollectionFolder) if (this is ICollectionFolder)
@ -137,9 +202,6 @@ namespace MediaBrowser.Controller.Entities
return true; return true;
} }
[JsonIgnore]
protected virtual bool SupportsShortcutChildren => false;
/// <summary> /// <summary>
/// Adds the child. /// Adds the child.
/// </summary> /// </summary>
@ -169,20 +231,6 @@ namespace MediaBrowser.Controller.Entities
LibraryManager.CreateItem(item, this); LibraryManager.CreateItem(item, this);
} }
/// <summary>
/// Gets the actual children.
/// </summary>
/// <value>The actual children.</value>
[JsonIgnore]
public virtual IEnumerable<BaseItem> Children => LoadChildren();
/// <summary>
/// thread-safe access to all recursive children of this folder - without regard to user.
/// </summary>
/// <value>The recursive children.</value>
[JsonIgnore]
public IEnumerable<BaseItem> RecursiveChildren => GetRecursiveChildren();
public override bool IsVisible(User user) public override bool IsVisible(User user)
{ {
if (this is ICollectionFolder && !(this is BasePluginFolder)) if (this is ICollectionFolder && !(this is BasePluginFolder))
@ -1428,8 +1476,6 @@ namespace MediaBrowser.Controller.Entities
return list; return list;
} }
protected virtual bool FilterLinkedChildrenPerUser => false;
public bool ContainsLinkedChildByItemId(Guid itemId) public bool ContainsLinkedChildByItemId(Guid itemId)
{ {
var linkedChildren = LinkedChildren; var linkedChildren = LinkedChildren;
@ -1530,9 +1576,6 @@ namespace MediaBrowser.Controller.Entities
.Where(i => i.Item2 != null); .Where(i => i.Item2 != null);
} }
[JsonIgnore]
protected override bool SupportsOwnedItems => base.SupportsOwnedItems || SupportsShortcutChildren;
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken) protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{ {
var changesFound = false; var changesFound = false;
@ -1696,51 +1739,6 @@ namespace MediaBrowser.Controller.Entities
return !IsPlayed(user); return !IsPlayed(user);
} }
[JsonIgnore]
public virtual bool SupportsUserDataFromChildren
{
get
{
// These are just far too slow.
if (this is ICollectionFolder)
{
return false;
}
if (this is UserView)
{
return false;
}
if (this is UserRootFolder)
{
return false;
}
if (this is Channel)
{
return false;
}
if (SourceType != SourceType.Library)
{
return false;
}
var iItemByName = this as IItemByName;
if (iItemByName != null)
{
var hasDualAccess = this as IHasDualAccess;
if (hasDualAccess == null || hasDualAccess.IsAccessedByName)
{
return false;
}
}
return true;
}
}
public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields) public override void FillUserDataDtoValues(UserItemDataDto dto, UserItemData userData, BaseItemDto itemDto, User user, DtoOptions fields)
{ {
if (!SupportsUserDataFromChildren) if (!SupportsUserDataFromChildren)

@ -16,6 +16,23 @@ namespace MediaBrowser.Controller.Entities
/// </summary> /// </summary>
public class Genre : BaseItem, IItemByName public class Genre : BaseItem, IItemByName
{ {
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
[JsonIgnore]
public override bool IsDisplayedAsFolder => true;
[JsonIgnore]
public override bool SupportsAncestors => false;
[JsonIgnore]
public override bool SupportsPeople => false;
public override List<string> GetUserDataKeys() public override List<string> GetUserDataKeys()
{ {
var list = base.GetUserDataKeys(); var list = base.GetUserDataKeys();
@ -34,20 +51,6 @@ namespace MediaBrowser.Controller.Entities
return 1; return 1;
} }
/// <summary>
/// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself.
/// </summary>
/// <value>The containing folder path.</value>
[JsonIgnore]
public override string ContainingFolderPath => Path;
[JsonIgnore]
public override bool IsDisplayedAsFolder => true;
[JsonIgnore]
public override bool SupportsAncestors => false;
public override bool IsSaveLocalMetadataEnabled() public override bool IsSaveLocalMetadataEnabled()
{ {
return true; return true;
@ -72,9 +75,6 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemList(query); return LibraryManager.GetItemList(query);
} }
[JsonIgnore]
public override bool SupportsPeople => false;
public static string GetPath(string name) public static string GetPath(string name)
{ {
return GetPath(name, true); return GetPath(name, true);
@ -107,12 +107,10 @@ namespace MediaBrowser.Controller.Entities
return base.RequiresRefresh(); return base.RequiresRefresh();
} }
/// <summary> /// <inheridoc />
/// This is called before any metadata refresh and returns true or false indicating if changes were made. public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
/// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
var newPath = GetRebasedPath(); var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal)) if (!string.Equals(Path, newPath, StringComparison.Ordinal))

@ -9,7 +9,7 @@ namespace MediaBrowser.Controller.Entities
public interface IHasSeries public interface IHasSeries
{ {
/// <summary> /// <summary>
/// Gets the name of the series. /// Gets or sets the name of the series.
/// </summary> /// </summary>
/// <value>The name of the series.</value> /// <value>The name of the series.</value>
string SeriesName { get; set; } string SeriesName { get; set; }

@ -0,0 +1,11 @@
#nullable disable
#pragma warning disable CS1591
namespace MediaBrowser.Controller.Entities
{
public interface IHasShares
{
Share[] Shares { get; set; }
}
}

@ -1,5 +1,3 @@
#nullable disable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -20,9 +18,9 @@ namespace MediaBrowser.Controller.Entities
public int? Limit { get; set; } public int? Limit { get; set; }
public User User { get; set; } public User? User { get; set; }
public BaseItem SimilarTo { get; set; } public BaseItem? SimilarTo { get; set; }
public bool? IsFolder { get; set; } public bool? IsFolder { get; set; }
@ -58,23 +56,23 @@ namespace MediaBrowser.Controller.Entities
public bool? CollapseBoxSetItems { get; set; } public bool? CollapseBoxSetItems { get; set; }
public string NameStartsWithOrGreater { get; set; } public string? NameStartsWithOrGreater { get; set; }
public string NameStartsWith { get; set; } public string? NameStartsWith { get; set; }
public string NameLessThan { get; set; } public string? NameLessThan { get; set; }
public string NameContains { get; set; } public string? NameContains { get; set; }
public string MinSortName { get; set; } public string? MinSortName { get; set; }
public string PresentationUniqueKey { get; set; } public string? PresentationUniqueKey { get; set; }
public string Path { get; set; } public string? Path { get; set; }
public string Name { get; set; } public string? Name { get; set; }
public string Person { get; set; } public string? Person { get; set; }
public Guid[] PersonIds { get; set; } public Guid[] PersonIds { get; set; }
@ -82,7 +80,7 @@ namespace MediaBrowser.Controller.Entities
public Guid[] ExcludeItemIds { get; set; } public Guid[] ExcludeItemIds { get; set; }
public string AdjacentTo { get; set; } public string? AdjacentTo { get; set; }
public string[] PersonTypes { get; set; } public string[] PersonTypes { get; set; }
@ -182,13 +180,13 @@ namespace MediaBrowser.Controller.Entities
public Guid ParentId { get; set; } public Guid ParentId { get; set; }
public string ParentType { get; set; } public string? ParentType { get; set; }
public Guid[] AncestorIds { get; set; } public Guid[] AncestorIds { get; set; }
public Guid[] TopParentIds { get; set; } public Guid[] TopParentIds { get; set; }
public BaseItem Parent public BaseItem? Parent
{ {
set set
{ {
@ -213,9 +211,9 @@ namespace MediaBrowser.Controller.Entities
public SeriesStatus[] SeriesStatuses { get; set; } public SeriesStatus[] SeriesStatuses { get; set; }
public string ExternalSeriesId { get; set; } public string? ExternalSeriesId { get; set; }
public string ExternalId { get; set; } public string? ExternalId { get; set; }
public Guid[] AlbumIds { get; set; } public Guid[] AlbumIds { get; set; }
@ -223,9 +221,9 @@ namespace MediaBrowser.Controller.Entities
public Guid[] ExcludeArtistIds { get; set; } public Guid[] ExcludeArtistIds { get; set; }
public string AncestorWithPresentationUniqueKey { get; set; } public string? AncestorWithPresentationUniqueKey { get; set; }
public string SeriesPresentationUniqueKey { get; set; } public string? SeriesPresentationUniqueKey { get; set; }
public bool GroupByPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; }
@ -235,7 +233,7 @@ namespace MediaBrowser.Controller.Entities
public bool ForceDirect { get; set; } public bool ForceDirect { get; set; }
public Dictionary<string, string> ExcludeProviderIds { get; set; } public Dictionary<string, string>? ExcludeProviderIds { get; set; }
public bool EnableGroupByMetadataKey { get; set; } public bool EnableGroupByMetadataKey { get; set; }
@ -253,13 +251,13 @@ namespace MediaBrowser.Controller.Entities
public int MinSimilarityScore { get; set; } public int MinSimilarityScore { get; set; }
public string HasNoAudioTrackWithLanguage { get; set; } public string? HasNoAudioTrackWithLanguage { get; set; }
public string HasNoInternalSubtitleTrackWithLanguage { get; set; } public string? HasNoInternalSubtitleTrackWithLanguage { get; set; }
public string HasNoExternalSubtitleTrackWithLanguage { get; set; } public string? HasNoExternalSubtitleTrackWithLanguage { get; set; }
public string HasNoSubtitleTrackWithLanguage { get; set; } public string? HasNoSubtitleTrackWithLanguage { get; set; }
public bool? IsDeadArtist { get; set; } public bool? IsDeadArtist { get; set; }
@ -283,12 +281,10 @@ namespace MediaBrowser.Controller.Entities
ExcludeInheritedTags = Array.Empty<string>(); ExcludeInheritedTags = Array.Empty<string>();
ExcludeItemIds = Array.Empty<Guid>(); ExcludeItemIds = Array.Empty<Guid>();
ExcludeItemTypes = Array.Empty<string>(); ExcludeItemTypes = Array.Empty<string>();
ExcludeProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ExcludeTags = Array.Empty<string>(); ExcludeTags = Array.Empty<string>();
GenreIds = Array.Empty<Guid>(); GenreIds = Array.Empty<Guid>();
Genres = Array.Empty<string>(); Genres = Array.Empty<string>();
GroupByPresentationUniqueKey = true; GroupByPresentationUniqueKey = true;
HasAnyProviderId = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
ImageTypes = Array.Empty<ImageType>(); ImageTypes = Array.Empty<ImageType>();
IncludeItemTypes = Array.Empty<string>(); IncludeItemTypes = Array.Empty<string>();
ItemIds = Array.Empty<Guid>(); ItemIds = Array.Empty<Guid>();
@ -309,32 +305,33 @@ namespace MediaBrowser.Controller.Entities
Years = Array.Empty<int>(); Years = Array.Empty<int>();
} }
public InternalItemsQuery(User user) public InternalItemsQuery(User? user)
: this() : this()
{ {
SetUser(user); if (user != null)
{
SetUser(user);
}
} }
public void SetUser(User user) public void SetUser(User user)
{ {
if (user != null) MaxParentalRating = user.MaxParentalAgeRating;
{
MaxParentalRating = user.MaxParentalAgeRating;
if (MaxParentalRating.HasValue) if (MaxParentalRating.HasValue)
{ {
BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems) string other = UnratedItem.Other.ToString();
.Where(i => i != UnratedItem.Other.ToString()) BlockUnratedItems = user.GetPreference(PreferenceKind.BlockUnratedItems)
.Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray(); .Where(i => i != other)
} .Select(e => Enum.Parse<UnratedItem>(e, true)).ToArray();
}
ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags); ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
User = user; User = user;
}
} }
public Dictionary<string, string> HasAnyProviderId { get; set; } public Dictionary<string, string>? HasAnyProviderId { get; set; }
public Guid[] AlbumArtistIds { get; set; } public Guid[] AlbumArtistIds { get; set; }
@ -356,8 +353,8 @@ namespace MediaBrowser.Controller.Entities
public int? MinWidth { get; set; } public int? MinWidth { get; set; }
public string SearchTerm { get; set; } public string? SearchTerm { get; set; }
public string SeriesTimerId { get; set; } public string? SeriesTimerId { get; set; }
} }
} }

@ -144,9 +144,9 @@ namespace MediaBrowser.Controller.Entities.Movies
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (!ProductionYear.HasValue) if (!ProductionYear.HasValue)
{ {

@ -36,9 +36,9 @@ namespace MediaBrowser.Controller.Entities
return info; return info;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (!ProductionYear.HasValue) if (!ProductionYear.HasValue)
{ {

@ -50,7 +50,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Returns the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
/// </summary> /// </summary>
/// <value>The containing folder path.</value> /// <value>The containing folder path.</value>
@ -67,6 +67,9 @@ namespace MediaBrowser.Controller.Entities
return true; return true;
} }
/// <summary>
/// Gets a value indicating whether to enable alpha numeric sorting.
/// </summary>
[JsonIgnore] [JsonIgnore]
public override bool EnableAlphaNumericSorting => false; public override bool EnableAlphaNumericSorting => false;
@ -126,9 +129,9 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
var newPath = GetRebasedPath(); var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal)) if (!string.Equals(Path, newPath, StringComparison.Ordinal))

@ -4,11 +4,6 @@
namespace MediaBrowser.Controller.Entities namespace MediaBrowser.Controller.Entities
{ {
public interface IHasShares
{
Share[] Shares { get; set; }
}
public class Share public class Share
{ {
public string UserId { get; set; } public string UserId { get; set; }

@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Returns the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
/// </summary> /// </summary>
/// <value>The containing folder path.</value> /// <value>The containing folder path.</value>
@ -105,9 +105,9 @@ namespace MediaBrowser.Controller.Entities
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
var newPath = GetRebasedPath(); var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal)) if (!string.Equals(Path, newPath, StringComparison.Ordinal))

@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Entities.TV
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
/// <summary> /// <summary>
/// Gets the season in which it aired. /// Gets or sets the season in which it aired.
/// </summary> /// </summary>
/// <value>The aired season.</value> /// <value>The aired season.</value>
public int? AirsBeforeSeasonNumber { get; set; } public int? AirsBeforeSeasonNumber { get; set; }
@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV
public int? AirsBeforeEpisodeNumber { get; set; } public int? AirsBeforeEpisodeNumber { get; set; }
/// <summary> /// <summary>
/// This is the ending episode number for double episodes. /// Gets or sets the ending episode number for double episodes.
/// </summary> /// </summary>
/// <value>The index number.</value> /// <value>The index number.</value>
public int? IndexNumberEnd { get; set; } public int? IndexNumberEnd { get; set; }
@ -116,7 +116,7 @@ namespace MediaBrowser.Controller.Entities.TV
} }
/// <summary> /// <summary>
/// This Episode's Series Instance. /// Gets the Episode's Series Instance.
/// </summary> /// </summary>
/// <value>The series.</value> /// <value>The series.</value>
[JsonIgnore] [JsonIgnore]
@ -261,6 +261,7 @@ namespace MediaBrowser.Controller.Entities.TV
[JsonIgnore] [JsonIgnore]
public Guid SeasonId { get; set; } public Guid SeasonId { get; set; }
[JsonIgnore] [JsonIgnore]
public Guid SeriesId { get; set; } public Guid SeriesId { get; set; }
@ -318,9 +319,9 @@ namespace MediaBrowser.Controller.Entities.TV
return id; return id;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (!IsLocked) if (!IsLocked)
{ {
@ -328,7 +329,7 @@ namespace MediaBrowser.Controller.Entities.TV
{ {
try try
{ {
if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetdata)) if (LibraryManager.FillMissingEpisodeNumbersFromPath(this, replaceAllMetadata))
{ {
hasChanges = true; hasChanges = true;
} }

@ -81,7 +81,7 @@ namespace MediaBrowser.Controller.Entities.TV
} }
/// <summary> /// <summary>
/// This Episode's Series Instance. /// Gets this Episode's Series Instance.
/// </summary> /// </summary>
/// <value>The series.</value> /// <value>The series.</value>
[JsonIgnore] [JsonIgnore]
@ -242,9 +242,9 @@ namespace MediaBrowser.Controller.Entities.TV
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true or false indicating if changes were made.
/// </summary> /// </summary>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path)) if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
{ {

@ -59,8 +59,11 @@ namespace MediaBrowser.Controller.Entities.TV
public IReadOnlyList<Guid> RemoteTrailerIds { get; set; } public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
/// <summary> /// <summary>
/// airdate, dvd or absolute. /// Gets or sets the display order.
/// </summary> /// </summary>
/// <remarks>
/// Valid options are airdate, dvd or absolute.
/// </remarks>
public string DisplayOrder { get; set; } public string DisplayOrder { get; set; }
/// <summary> /// <summary>

@ -45,9 +45,9 @@ namespace MediaBrowser.Controller.Entities
return info; return info;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (!ProductionYear.HasValue) if (!ProductionYear.HasValue)
{ {

@ -96,7 +96,7 @@ namespace MediaBrowser.Controller.Entities
public const double MinLikeValue = 6.5; public const double MinLikeValue = 6.5;
/// <summary> /// <summary>
/// This is an interpreted property to indicate likes or dislikes /// Gets or sets a value indicating whether the item is liked or not.
/// This should never be serialized. /// This should never be serialized.
/// </summary> /// </summary>
/// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value> /// <value><c>null</c> if [likes] contains no value, <c>true</c> if [likes]; otherwise, <c>false</c>.</value>

@ -23,6 +23,7 @@ namespace MediaBrowser.Controller.Entities
{ {
private List<Guid> _childrenIds = null; private List<Guid> _childrenIds = null;
private readonly object _childIdsLock = new object(); private readonly object _childIdsLock = new object();
protected override List<BaseItem> LoadChildren() protected override List<BaseItem> LoadChildren()
{ {
lock (_childIdsLock) lock (_childIdsLock)
@ -87,10 +88,10 @@ namespace MediaBrowser.Controller.Entities
return list; return list;
} }
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
ClearCache(); ClearCache();
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase))
{ {

@ -15,13 +15,19 @@ namespace MediaBrowser.Controller.Entities
{ {
public class UserView : Folder, IHasCollectionType public class UserView : Folder, IHasCollectionType
{ {
/// <inheritdoc /> /// <summary>
/// Gets or sets the view type.
/// </summary>
public string ViewType { get; set; } public string ViewType { get; set; }
/// <inheritdoc /> /// <summary>
/// Gets or sets the display parent id.
/// </summary>
public new Guid DisplayParentId { get; set; } public new Guid DisplayParentId { get; set; }
/// <inheritdoc /> /// <summary>
/// Gets or sets the user id.
/// </summary>
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
public static ITVSeriesManager TVSeriesManager; public static ITVSeriesManager TVSeriesManager;
@ -110,10 +116,10 @@ namespace MediaBrowser.Controller.Entities
return GetChildren(user, false); return GetChildren(user, false);
} }
private static string[] UserSpecificViewTypes = new string[] private static readonly string[] UserSpecificViewTypes = new string[]
{ {
Model.Entities.CollectionType.Playlists Model.Entities.CollectionType.Playlists
}; };
public static bool IsUserSpecific(Folder folder) public static bool IsUserSpecific(Folder folder)
{ {

@ -24,7 +24,7 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// Returns the folder containing the item. /// Gets the folder containing the item.
/// If the item is a folder, it returns the folder itself. /// If the item is a folder, it returns the folder itself.
/// </summary> /// </summary>
/// <value>The containing folder path.</value> /// <value>The containing folder path.</value>
@ -112,11 +112,13 @@ namespace MediaBrowser.Controller.Entities
} }
/// <summary> /// <summary>
/// This is called before any metadata refresh and returns true or false indicating if changes were made. /// This is called before any metadata refresh and returns true if changes were made.
/// </summary> /// </summary>
public override bool BeforeMetadataRefresh(bool replaceAllMetdata) /// <param name="replaceAllMetadata">Whether to replace all metadata.</param>
/// <returns>true if the item has change, else false.</returns>
public override bool BeforeMetadataRefresh(bool replaceAllMetadata)
{ {
var hasChanges = base.BeforeMetadataRefresh(replaceAllMetdata); var hasChanges = base.BeforeMetadataRefresh(replaceAllMetadata);
var newPath = GetRebasedPath(); var newPath = GetRebasedPath();
if (!string.Equals(Path, newPath, StringComparison.Ordinal)) if (!string.Equals(Path, newPath, StringComparison.Ordinal))

@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.Extensions
{ {
// will throw if input contains invalid unicode chars // will throw if input contains invalid 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/
text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])", ""); text = Regex.Replace(text, "([\ud800-\udbff](?![\udc00-\udfff]))|((?<![\ud800-\udbff])[\udc00-\udfff])", string.Empty);
return Normalize(text, form, false); return Normalize(text, form, false);
} }
} }

@ -43,6 +43,12 @@ namespace MediaBrowser.Controller.Library
/// <summary> /// <summary>
/// Resolves a set of files into a list of BaseItem. /// Resolves a set of files into a list of BaseItem.
/// </summary> /// </summary>
/// <param name="files">The list of tiles.</param>
/// <param name="directoryService">Instance of the <see cref="IDirectoryService"/> interface.</param>
/// <param name="parent">The parent folder.</param>
/// <param name="libraryOptions">The library options.</param>
/// <param name="collectionType">The collection type.</param>
/// <returns>The items resolved from the paths.</returns>
IEnumerable<BaseItem> ResolvePaths( IEnumerable<BaseItem> ResolvePaths(
IEnumerable<FileSystemMetadata> files, IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService, IDirectoryService directoryService,

@ -148,7 +148,7 @@ namespace MediaBrowser.Controller.LiveTv
public bool IsNews { get; set; } public bool IsNews { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is kids. /// Gets a value indicating whether this instance is kids.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
[JsonIgnore] [JsonIgnore]

@ -71,7 +71,7 @@ namespace MediaBrowser.Controller.LiveTv
public override SourceType SourceType => SourceType.LiveTV; public override SourceType SourceType => SourceType.LiveTV;
/// <summary> /// <summary>
/// The start date of the program, in UTC. /// Gets or sets start date of the program, in UTC.
/// </summary> /// </summary>
[JsonIgnore] [JsonIgnore]
public DateTime StartDate { get; set; } public DateTime StartDate { get; set; }

@ -28,18 +28,17 @@ namespace MediaBrowser.Controller.LiveTv
public string[] Tags { get; set; } public string[] Tags { get; set; }
/// <summary> /// <summary>
/// Id of the recording. /// Gets or sets the id of the recording.
/// </summary> /// </summary>
public string Id { get; set; } public string Id { get; set; }
/// <summary> /// <summary>
/// Gets or sets the series timer identifier. /// Gets or sets the series timer identifier.
/// </summary> /// </summary>
/// <value>The series timer identifier.</value>
public string SeriesTimerId { get; set; } public string SeriesTimerId { get; set; }
/// <summary> /// <summary>
/// ChannelId of the recording. /// Gets or sets the channelId of the recording.
/// </summary> /// </summary>
public string ChannelId { get; set; } public string ChannelId { get; set; }
@ -52,24 +51,24 @@ namespace MediaBrowser.Controller.LiveTv
public string ShowId { get; set; } public string ShowId { get; set; }
/// <summary> /// <summary>
/// Name of the recording. /// Gets or sets the name of the recording.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// Description of the recording. /// Gets or sets the description of the recording.
/// </summary> /// </summary>
public string Overview { get; set; } public string Overview { get; set; }
public string SeriesId { get; set; } public string SeriesId { get; set; }
/// <summary> /// <summary>
/// The start date of the recording, in UTC. /// Gets or sets the start date of the recording, in UTC.
/// </summary> /// </summary>
public DateTime StartDate { get; set; } public DateTime StartDate { get; set; }
/// <summary> /// <summary>
/// The end date of the recording, in UTC. /// Gets or sets the end date of the recording, in UTC.
/// </summary> /// </summary>
public DateTime EndDate { get; set; } public DateTime EndDate { get; set; }
@ -133,7 +132,7 @@ namespace MediaBrowser.Controller.LiveTv
public bool IsSeries { get; set; } public bool IsSeries { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this instance is live. /// Gets a value indicating whether this instance is live.
/// </summary> /// </summary>
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value> /// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
[JsonIgnore] [JsonIgnore]

@ -596,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
&& string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& isNvdecDecoder) && isNvdecDecoder)
{ {
arg.Append("-hwaccel_output_format cuda -autorotate 0 "); // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 ");
} }
if (state.IsVideoRequest if (state.IsVideoRequest
@ -1070,7 +1071,6 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc) else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) // h264 (h264_nvenc)
|| string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc) || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) // hevc (hevc_nvenc)
{ {
// following preset will be deprecated in ffmpeg 4.4, use p1~p7 instead.
switch (encodingOptions.EncoderPreset) switch (encodingOptions.EncoderPreset)
{ {
case "veryslow": case "veryslow":
@ -1251,7 +1251,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase) if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
&& profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase)) && profile.Contains("baseline", StringComparison.OrdinalIgnoreCase))
{ {
profile = "constrained_baseline"; profile = "constrained_baseline";
} }
@ -2933,6 +2933,7 @@ namespace MediaBrowser.Controller.MediaEncoding
return threads; return threads;
} }
#nullable disable #nullable disable
public void TryStreamCopy(EncodingJobInfo state) public void TryStreamCopy(EncodingJobInfo state)
{ {

@ -430,7 +430,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Predicts the audio sample rate that will be in the output stream. /// Gets the target video level.
/// </summary> /// </summary>
public double? TargetVideoLevel public double? TargetVideoLevel
{ {
@ -453,7 +453,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Predicts the audio sample rate that will be in the output stream. /// Gets the target video bit depth.
/// </summary> /// </summary>
public int? TargetVideoBitDepth public int? TargetVideoBitDepth
{ {
@ -488,7 +488,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Predicts the audio sample rate that will be in the output stream. /// Gets the target framerate.
/// </summary> /// </summary>
public float? TargetFramerate public float? TargetFramerate
{ {
@ -520,7 +520,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Predicts the audio sample rate that will be in the output stream. /// Gets the target packet length.
/// </summary> /// </summary>
public int? TargetPacketLength public int? TargetPacketLength
{ {
@ -536,7 +536,7 @@ namespace MediaBrowser.Controller.MediaEncoding
} }
/// <summary> /// <summary>
/// Predicts the audio sample rate that will be in the output stream. /// Gets the target video profile.
/// </summary> /// </summary>
public string TargetVideoProfile public string TargetVideoProfile
{ {
@ -700,25 +700,4 @@ namespace MediaBrowser.Controller.MediaEncoding
Progress.Report(percentComplete.Value); Progress.Report(percentComplete.Value);
} }
} }
/// <summary>
/// Enum TranscodingJobType.
/// </summary>
public enum TranscodingJobType
{
/// <summary>
/// The progressive.
/// </summary>
Progressive,
/// <summary>
/// The HLS.
/// </summary>
Hls,
/// <summary>
/// The dash.
/// </summary>
Dash
}
} }

@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public interface IMediaEncoder : ITranscoderSupport public interface IMediaEncoder : ITranscoderSupport
{ {
/// <summary> /// <summary>
/// The location of the discovered FFmpeg tool. /// Gets location of the discovered FFmpeg tool.
/// </summary> /// </summary>
FFmpegLocation EncoderLocation { get; } FFmpegLocation EncoderLocation { get; }

@ -0,0 +1,23 @@
namespace MediaBrowser.Controller.MediaEncoding
{
/// <summary>
/// Enum TranscodingJobType.
/// </summary>
public enum TranscodingJobType
{
/// <summary>
/// The progressive.
/// </summary>
Progressive,
/// <summary>
/// The HLS.
/// </summary>
Hls,
/// <summary>
/// The dash.
/// </summary>
Dash
}
}

@ -22,14 +22,14 @@ namespace MediaBrowser.Controller.Playlists
{ {
public class Playlist : Folder, IHasShares public class Playlist : Folder, IHasShares
{ {
public static string[] SupportedExtensions = public static readonly IReadOnlyList<string> SupportedExtensions = new[]
{ {
".m3u", ".m3u",
".m3u8", ".m3u8",
".pls", ".pls",
".wpl", ".wpl",
".zpl" ".zpl"
}; };
public Guid OwnerUserId { get; set; } public Guid OwnerUserId { get; set; }

@ -0,0 +1,9 @@
namespace MediaBrowser.Controller.Plugins
{
/// <summary>
/// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
/// </summary>
public interface IRunBeforeStartup
{
}
}

@ -14,13 +14,7 @@ namespace MediaBrowser.Controller.Plugins
/// <summary> /// <summary>
/// Run the initialization for this module. This method is invoked at application start. /// Run the initialization for this module. This method is invoked at application start.
/// </summary> /// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
Task RunAsync(); Task RunAsync();
} }
/// <summary>
/// Indicates that a <see cref="IServerEntryPoint"/> should be invoked as a pre-startup task.
/// </summary>
public interface IRunBeforeStartup
{
}
} }

@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.Providers
/// <summary> /// <summary>
/// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers /// Gets or sets a value indicating whether all existing data should be overwritten with new data from providers
/// when paired with MetadataRefreshMode=FullRefresh /// when paired with MetadataRefreshMode=FullRefresh.
/// </summary> /// </summary>
public bool ReplaceAllMetadata { get; set; } public bool ReplaceAllMetadata { get; set; }

@ -1,22 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Sync;
namespace MediaBrowser.Controller.Sync
{
public interface IHasDynamicAccess
{
/// <summary>
/// Gets the synced file information.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="target">The target.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task&lt;SyncedFileInfo&gt;.</returns>
Task<SyncedFileInfo> GetSyncedFileInfo(string id, SyncTarget target, CancellationToken cancellationToken);
}
}

@ -1,9 +0,0 @@
namespace MediaBrowser.Controller.Sync
{
/// <summary>
/// A marker interface.
/// </summary>
public interface IRemoteSyncProvider
{
}
}

@ -1,32 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Sync;
namespace MediaBrowser.Controller.Sync
{
public interface IServerSyncProvider : ISyncProvider
{
/// <summary>
/// Transfers the file.
/// </summary>
Task<SyncedFileInfo> SendFile(SyncJob syncJob, string originalMediaPath, Stream inputStream, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
Task<QueryResult<FileSystemMetadata>> GetFiles(string[] directoryPathParts, SyncTarget target, CancellationToken cancellationToken);
}
public interface ISupportsDirectCopy
{
/// <summary>
/// Sends the file.
/// </summary>
Task<SyncedFileInfo> SendFile(SyncJob syncJob, string originalMediaPath, string inputPath, bool isMedia, string[] outputPathParts, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken);
}
}

@ -1,31 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Model.Sync;
namespace MediaBrowser.Controller.Sync
{
public interface ISyncProvider
{
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
string Name { get; }
/// <summary>
/// Gets the synchronize targets.
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
List<SyncTarget> GetSyncTargets(string userId);
/// <summary>
/// Gets all synchronize targets.
/// </summary>
/// <returns>IEnumerable&lt;SyncTarget&gt;.</returns>
List<SyncTarget> GetAllSyncTargets();
}
}

@ -1,43 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System.Collections.Generic;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Controller.Sync
{
public class SyncedFileInfo
{
public SyncedFileInfo()
{
RequiredHttpHeaders = new Dictionary<string, string>();
}
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public string Path { get; set; }
public string[] PathParts { get; set; }
/// <summary>
/// Gets or sets the protocol.
/// </summary>
/// <value>The protocol.</value>
public MediaProtocol Protocol { get; set; }
/// <summary>
/// Gets or sets the required HTTP headers.
/// </summary>
/// <value>The required HTTP headers.</value>
public Dictionary<string, string> RequiredHttpHeaders { get; set; }
/// <summary>
/// Gets or sets the identifier.
/// </summary>
/// <value>The identifier.</value>
public string Id { get; set; }
}
}

@ -6,16 +6,26 @@ using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.TV namespace MediaBrowser.Controller.TV
{ {
/// <summary>
/// The TV Series manager.
/// </summary>
public interface ITVSeriesManager public interface ITVSeriesManager
{ {
/// <summary> /// <summary>
/// Gets the next up. /// Gets the next up.
/// </summary> /// </summary>
/// <param name="query">The next up query.</param>
/// <param name="options">The dto options.</param>
/// <returns>The next up items.</returns>
QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options); QueryResult<BaseItem> GetNextUp(NextUpQuery query, DtoOptions options);
/// <summary> /// <summary>
/// Gets the next up. /// Gets the next up.
/// </summary> /// </summary>
/// <param name="request">The next up request.</param>
/// <param name="parentsFolders">The list of parent folders.</param>
/// <param name="options">The dto options.</param>
/// <returns>The next up items.</returns>
QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options); QueryResult<BaseItem> GetNextUp(NextUpQuery request, BaseItem[] parentsFolders, DtoOptions options);
} }
} }

@ -466,7 +466,7 @@ namespace MediaBrowser.LocalMetadata.Images
return added; return added;
} }
private bool AddImage(IEnumerable<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type) private bool AddImage(List<FileSystemMetadata> files, List<LocalImageInfo> images, string name, ImageType type)
{ {
var image = GetImage(files, name); var image = GetImage(files, name);
@ -484,9 +484,20 @@ namespace MediaBrowser.LocalMetadata.Images
return false; return false;
} }
private FileSystemMetadata? GetImage(IEnumerable<FileSystemMetadata> files, string name) private static FileSystemMetadata? GetImage(IReadOnlyList<FileSystemMetadata> files, string name)
{ {
return files.FirstOrDefault(i => !i.IsDirectory && string.Equals(name, _fileSystem.GetFileNameWithoutExtension(i), StringComparison.OrdinalIgnoreCase) && i.Length > 0); for (var i = 0; i < files.Count; i++)
{
var file = files[i];
if (!file.IsDirectory
&& file.Length > 0
&& Path.GetFileNameWithoutExtension(file.FullName.AsSpan()).Equals(name, StringComparison.OrdinalIgnoreCase))
{
return file;
}
}
return null;
} }
} }
} }

@ -15,17 +15,6 @@ namespace MediaBrowser.Providers.MediaInfo
{ {
private readonly ILocalizationManager _localization; private readonly ILocalizationManager _localization;
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
".srt",
".ssa",
".ass",
".sub",
".smi",
".sami",
".vtt"
};
public SubtitleResolver(ILocalizationManager localization) public SubtitleResolver(ILocalizationManager localization)
{ {
_localization = localization; _localization = localization;
@ -88,80 +77,65 @@ namespace MediaBrowser.Providers.MediaInfo
return list; return list;
} }
private void AddExternalSubtitleStreams(
List<MediaStream> streams,
string folder,
string videoPath,
int startIndex,
IDirectoryService directoryService,
bool clearCache)
{
var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray();
AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
}
public void AddExternalSubtitleStreams( public void AddExternalSubtitleStreams(
List<MediaStream> streams, List<MediaStream> streams,
string videoPath, string videoPath,
int startIndex, int startIndex,
string[] files) string[] files)
{ {
var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(videoPath); var videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoPath);
videoFileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(videoFileNameWithoutExtension);
foreach (var fullName in files) foreach (var fullName in files)
{ {
var extension = Path.GetExtension(fullName); var extension = Path.GetExtension(fullName.AsSpan());
if (!IsSubtitleExtension(extension))
if (!SubtitleExtensions.Contains(extension))
{ {
continue; continue;
} }
var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName); var fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fullName);
fileNameWithoutExtension = NormalizeFilenameForSubtitleComparison(fileNameWithoutExtension);
if (!string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase) && MediaStream mediaStream;
!fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var codec = Path.GetExtension(fullName).ToLowerInvariant().TrimStart('.');
if (string.Equals(codec, "txt", StringComparison.OrdinalIgnoreCase))
{
codec = "srt";
}
// If the subtitle file matches the video file name // The subtitle filename must either be equal to the video filename or start with the video filename followed by a dot
if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase)) if (videoFileNameWithoutExtension.Equals(fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{ {
streams.Add(new MediaStream mediaStream = new MediaStream
{ {
Index = startIndex++, Index = startIndex++,
Type = MediaStreamType.Subtitle, Type = MediaStreamType.Subtitle,
IsExternal = true, IsExternal = true,
Path = fullName, Path = fullName
Codec = codec };
});
} }
else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase)) else if (fileNameWithoutExtension.Length > videoFileNameWithoutExtension.Length
&& fileNameWithoutExtension[videoFileNameWithoutExtension.Length] == '.'
&& fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
{ {
var isForced = fullName.IndexOf(".forced.", StringComparison.OrdinalIgnoreCase) != -1 || var isForced = fullName.Contains(".forced.", StringComparison.OrdinalIgnoreCase)
fullName.IndexOf(".foreign.", StringComparison.OrdinalIgnoreCase) != -1; || fullName.Contains(".foreign.", StringComparison.OrdinalIgnoreCase);
var isDefault = fullName.IndexOf(".default.", StringComparison.OrdinalIgnoreCase) != -1; var isDefault = fullName.Contains(".default.", StringComparison.OrdinalIgnoreCase);
// Support xbmc naming conventions - 300.spanish.srt // Support xbmc naming conventions - 300.spanish.srt
var language = fileNameWithoutExtension var languageSpan = fileNameWithoutExtension;
.Replace(".forced", string.Empty, StringComparison.OrdinalIgnoreCase) while (languageSpan.Length > 0)
.Replace(".foreign", string.Empty, StringComparison.OrdinalIgnoreCase) {
.Replace(".default", string.Empty, StringComparison.OrdinalIgnoreCase) var lastDot = languageSpan.LastIndexOf('.');
.Split('.') var currentSlice = languageSpan[lastDot..];
.LastOrDefault(); if (currentSlice.Equals(".default", StringComparison.OrdinalIgnoreCase)
|| currentSlice.Equals(".forced", StringComparison.OrdinalIgnoreCase)
|| currentSlice.Equals(".foreign", StringComparison.OrdinalIgnoreCase))
{
languageSpan = languageSpan[..lastDot];
continue;
}
languageSpan = languageSpan[(lastDot + 1)..];
break;
}
var language = languageSpan.ToString();
// Try to translate to three character code // Try to translate to three character code
// Be flexible and check against both the full and three character versions // Be flexible and check against both the full and three character versions
var culture = _localization.FindLanguageInfo(language); var culture = _localization.FindLanguageInfo(language);
@ -171,33 +145,58 @@ namespace MediaBrowser.Providers.MediaInfo
language = culture.ThreeLetterISOLanguageName; language = culture.ThreeLetterISOLanguageName;
} }
streams.Add(new MediaStream mediaStream = new MediaStream
{ {
Index = startIndex++, Index = startIndex++,
Type = MediaStreamType.Subtitle, Type = MediaStreamType.Subtitle,
IsExternal = true, IsExternal = true,
Path = fullName, Path = fullName,
Codec = codec,
Language = language, Language = language,
IsForced = isForced, IsForced = isForced,
IsDefault = isDefault IsDefault = isDefault
}); };
}
else
{
continue;
} }
mediaStream.Codec = extension.TrimStart('.').ToString().ToLowerInvariant();
streams.Add(mediaStream);
} }
} }
private string NormalizeFilenameForSubtitleComparison(string filename) private static bool IsSubtitleExtension(ReadOnlySpan<char> extension)
{
return extension.Equals(".srt", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".ssa", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".ass", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".sub", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".vtt", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".smi", StringComparison.OrdinalIgnoreCase)
|| extension.Equals(".sami", StringComparison.OrdinalIgnoreCase);
}
private static ReadOnlySpan<char> NormalizeFilenameForSubtitleComparison(string filename)
{ {
// Try to account for sloppy file naming // Try to account for sloppy file naming
filename = filename.Replace("_", string.Empty, StringComparison.Ordinal); filename = filename.Replace("_", string.Empty, StringComparison.Ordinal);
filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal); filename = filename.Replace(" ", string.Empty, StringComparison.Ordinal);
return Path.GetFileNameWithoutExtension(filename.AsSpan());
}
// can't normalize this due to languages such as pt-br private void AddExternalSubtitleStreams(
// filename = filename.Replace("-", string.Empty); List<MediaStream> streams,
string folder,
// filename = filename.Replace(".", string.Empty); string videoPath,
int startIndex,
IDirectoryService directoryService,
bool clearCache)
{
var files = directoryService.GetFilePaths(folder, clearCache).OrderBy(i => i).ToArray();
return filename; AddExternalSubtitleStreams(streams, videoPath, startIndex, files);
} }
} }
} }

@ -69,58 +69,52 @@ namespace MediaBrowser.Providers.Music
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream) private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
{ {
using (var oReader = new StreamReader(stream, Encoding.UTF8)) using var oReader = new StreamReader(stream, Encoding.UTF8);
var settings = new XmlReaderSettings()
{ {
var settings = new XmlReaderSettings() ValidationType = ValidationType.None,
{ CheckCharacters = false,
ValidationType = ValidationType.None, IgnoreProcessingInstructions = true,
CheckCharacters = false, IgnoreComments = true
IgnoreProcessingInstructions = true, };
IgnoreComments = true
};
using (var reader = XmlReader.Create(oReader, settings)) using var reader = XmlReader.Create(oReader, settings);
{ reader.MoveToContent();
reader.MoveToContent(); reader.Read();
reader.Read();
// Loop through each element // Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive) while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{
switch (reader.Name)
{ {
if (reader.NodeType == XmlNodeType.Element) case "artist-list":
{ {
switch (reader.Name) if (reader.IsEmptyElement)
{ {
case "artist-list": reader.Read();
{ continue;
if (reader.IsEmptyElement)
{
reader.Read();
continue;
}
using (var subReader = reader.ReadSubtree())
{
return ParseArtistList(subReader).ToList();
}
}
default:
{
reader.Skip();
break;
}
} }
using var subReader = reader.ReadSubtree();
return ParseArtistList(subReader).ToList();
} }
else
default:
{ {
reader.Read(); reader.Skip();
break;
} }
} }
}
return Enumerable.Empty<RemoteSearchResult>(); else
{
reader.Read();
} }
} }
return Enumerable.Empty<RemoteSearchResult>();
} }
private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader) private IEnumerable<RemoteSearchResult> ParseArtistList(XmlReader reader)
@ -145,13 +139,11 @@ namespace MediaBrowser.Providers.Music
var mbzId = reader.GetAttribute("id"); var mbzId = reader.GetAttribute("id");
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
var artist = ParseArtist(subReader, mbzId);
if (artist != null)
{ {
var artist = ParseArtist(subReader, mbzId); yield return artist;
if (artist != null)
{
yield return artist;
}
} }
break; break;

@ -128,53 +128,49 @@ namespace MediaBrowser.Providers.Music
private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream) private IEnumerable<RemoteSearchResult> GetResultsFromResponse(Stream stream)
{ {
using (var oReader = new StreamReader(stream, Encoding.UTF8)) using var oReader = new StreamReader(stream, Encoding.UTF8);
var settings = new XmlReaderSettings()
{
ValidationType = ValidationType.None,
CheckCharacters = false,
IgnoreProcessingInstructions = true,
IgnoreComments = true
};
using var reader = XmlReader.Create(oReader, settings);
var results = ReleaseResult.Parse(reader);
return results.Select(i =>
{ {
var settings = new XmlReaderSettings() var result = new RemoteSearchResult
{ {
ValidationType = ValidationType.None, Name = i.Title,
CheckCharacters = false, ProductionYear = i.Year
IgnoreProcessingInstructions = true,
IgnoreComments = true
}; };
using (var reader = XmlReader.Create(oReader, settings)) if (i.Artists.Count > 0)
{ {
var results = ReleaseResult.Parse(reader); result.AlbumArtist = new RemoteSearchResult
return results.Select(i =>
{ {
var result = new RemoteSearchResult SearchProviderName = Name,
{ Name = i.Artists[0].Item1
Name = i.Title, };
ProductionYear = i.Year
};
if (i.Artists.Count > 0)
{
result.AlbumArtist = new RemoteSearchResult
{
SearchProviderName = Name,
Name = i.Artists[0].Item1
};
result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2); result.AlbumArtist.SetProviderId(MetadataProvider.MusicBrainzArtist, i.Artists[0].Item2);
} }
if (!string.IsNullOrWhiteSpace(i.ReleaseId))
{
result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
}
if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId)) if (!string.IsNullOrWhiteSpace(i.ReleaseId))
{ {
result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId); result.SetProviderId(MetadataProvider.MusicBrainzAlbum, i.ReleaseId);
} }
return result; if (!string.IsNullOrWhiteSpace(i.ReleaseGroupId))
}); {
result.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, i.ReleaseGroupId);
} }
}
return result;
});
} }
/// <inheritdoc /> /// <inheritdoc />
@ -339,10 +335,8 @@ namespace MediaBrowser.Providers.Music
continue; continue;
} }
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
{ return ParseReleaseList(subReader).ToList();
return ParseReleaseList(subReader).ToList();
}
} }
default: default:
@ -383,13 +377,11 @@ namespace MediaBrowser.Providers.Music
var releaseId = reader.GetAttribute("id"); var releaseId = reader.GetAttribute("id");
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
var release = ParseRelease(subReader, releaseId);
if (release != null)
{ {
var release = ParseRelease(subReader, releaseId); yield return release;
if (release != null)
{
yield return release;
}
} }
break; break;
@ -460,14 +452,12 @@ namespace MediaBrowser.Providers.Music
case "artist-credit": case "artist-credit":
{ {
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
{ var artist = ParseArtistCredit(subReader);
var artist = ParseArtistCredit(subReader);
if (!string.IsNullOrEmpty(artist.Item1)) if (!string.IsNullOrEmpty(artist.Item1))
{ {
result.Artists.Add(artist); result.Artists.Add(artist);
}
} }
break; break;
@ -505,12 +495,10 @@ namespace MediaBrowser.Providers.Music
switch (reader.Name) switch (reader.Name)
{ {
case "name-credit": case "name-credit":
{ {
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
{ return ParseArtistNameCredit(subReader);
return ParseArtistNameCredit(subReader); }
}
}
default: default:
{ {
@ -545,10 +533,8 @@ namespace MediaBrowser.Providers.Music
case "artist": case "artist":
{ {
var id = reader.GetAttribute("id"); var id = reader.GetAttribute("id");
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
{ return ParseArtistArtistCredit(subReader, id);
return ParseArtistArtistCredit(subReader, id);
}
} }
default: default:
@ -647,47 +633,43 @@ namespace MediaBrowser.Providers.Music
IgnoreComments = true IgnoreComments = true
}; };
using (var reader = XmlReader.Create(oReader, settings)) using var reader = XmlReader.Create(oReader, settings);
{ reader.MoveToContent();
reader.MoveToContent(); reader.Read();
reader.Read();
// Loop through each element // Loop through each element
while (!reader.EOF && reader.ReadState == ReadState.Interactive) while (!reader.EOF && reader.ReadState == ReadState.Interactive)
{
if (reader.NodeType == XmlNodeType.Element)
{ {
if (reader.NodeType == XmlNodeType.Element) switch (reader.Name)
{ {
switch (reader.Name) case "release-group-list":
{ {
case "release-group-list": if (reader.IsEmptyElement)
{ {
if (reader.IsEmptyElement) reader.Read();
{ continue;
reader.Read();
continue;
}
using (var subReader = reader.ReadSubtree())
{
return GetFirstReleaseGroupId(subReader);
}
} }
default: using var subReader = reader.ReadSubtree();
{ return GetFirstReleaseGroupId(subReader);
reader.Skip(); }
break;
} default:
{
reader.Skip();
break;
} }
}
else
{
reader.Read();
} }
} }
else
return null; {
reader.Read();
}
} }
return null;
} }
private string GetFirstReleaseGroupId(XmlReader reader) private string GetFirstReleaseGroupId(XmlReader reader)

@ -172,23 +172,19 @@ namespace MediaBrowser.Providers.Studios
public IEnumerable<string> GetAvailableImages(string file) public IEnumerable<string> GetAvailableImages(string file)
{ {
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read)) using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
using var reader = new StreamReader(fileStream);
var lines = new List<string>();
foreach (var line in reader.ReadAllLines())
{ {
using (var reader = new StreamReader(fileStream)) if (!string.IsNullOrWhiteSpace(line))
{ {
var lines = new List<string>(); lines.Add(line);
foreach (var line in reader.ReadAllLines())
{
if (!string.IsNullOrWhiteSpace(line))
{
lines.Add(line);
}
}
return lines;
} }
} }
return lines;
} }
} }
} }

@ -187,48 +187,46 @@ namespace MediaBrowser.Providers.Subtitles
{ {
var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia; var saveInMediaFolder = libraryOptions.SaveSubtitlesWithMedia;
using (var stream = response.Stream) using var stream = response.Stream;
using (var memoryStream = new MemoryStream()) using var memoryStream = new MemoryStream();
{ await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
await stream.CopyToAsync(memoryStream).ConfigureAwait(false); memoryStream.Position = 0;
memoryStream.Position = 0;
var savePaths = new List<string>(); var savePaths = new List<string>();
var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant(); var saveFileName = Path.GetFileNameWithoutExtension(video.Path) + "." + response.Language.ToLowerInvariant();
if (response.IsForced) if (response.IsForced)
{ {
saveFileName += ".forced"; saveFileName += ".forced";
} }
saveFileName += "." + response.Format.ToLowerInvariant(); saveFileName += "." + response.Format.ToLowerInvariant();
if (saveInMediaFolder) if (saveInMediaFolder)
{
var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName));
// TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal))
{ {
var mediaFolderPath = Path.GetFullPath(Path.Combine(video.ContainingFolderPath, saveFileName)); savePaths.Add(mediaFolderPath);
// TODO: Add some error handling to the API user: return BadRequest("Could not save subtitle, bad path.");
if (mediaFolderPath.StartsWith(video.ContainingFolderPath, StringComparison.Ordinal))
{
savePaths.Add(mediaFolderPath);
}
} }
}
var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName)); var internalPath = Path.GetFullPath(Path.Combine(video.GetInternalMetadataPath(), saveFileName));
// TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path."); // TODO: Add some error to the user: return BadRequest("Could not save subtitle, bad path.");
if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal)) if (internalPath.StartsWith(video.GetInternalMetadataPath(), StringComparison.Ordinal))
{ {
savePaths.Add(internalPath); savePaths.Add(internalPath);
} }
if (savePaths.Count > 0) if (savePaths.Count > 0)
{ {
await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false); await TrySaveToFiles(memoryStream, savePaths).ConfigureAwait(false);
} }
else else
{ {
_logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid."); _logger.LogError("An uploaded subtitle could not be saved because the resulting paths were invalid.");
}
} }
} }
@ -247,10 +245,8 @@ namespace MediaBrowser.Providers.Subtitles
Directory.CreateDirectory(Path.GetDirectoryName(savePath)); Directory.CreateDirectory(Path.GetDirectoryName(savePath));
// use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 . // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true)) using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, true);
{ await stream.CopyToAsync(fs).ConfigureAwait(false);
await stream.CopyToAsync(fs).ConfigureAwait(false);
}
return; return;
} }

12
debian/postinst vendored

@ -9,6 +9,8 @@ if [[ -f $DEFAULT_FILE ]]; then
. $DEFAULT_FILE . $DEFAULT_FILE
fi fi
JELLYFIN_USER=${JELLYFIN_USER:-jellyfin}
# Data directories for program data (cache, db), configs, and logs # Data directories for program data (cache, db), configs, and logs
PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME} PROGRAMDATA=${JELLYFIN_DATA_DIRECTORY-/var/lib/$NAME}
CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME} CONFIGDATA=${JELLYFIN_CONFIG_DIRECTORY-/etc/$NAME}
@ -18,12 +20,12 @@ CACHEDATA=${JELLYFIN_CACHE_DIRECTORY-/var/cache/$NAME}
case "$1" in case "$1" in
configure) configure)
# create jellyfin group if it does not exist # create jellyfin group if it does not exist
if [[ -z "$(getent group jellyfin)" ]]; then if [[ -z "$(getent group ${JELLYFIN_USER})" ]]; then
addgroup --quiet --system jellyfin > /dev/null 2>&1 addgroup --quiet --system ${JELLYFIN_USER} > /dev/null 2>&1
fi fi
# create jellyfin user if it does not exist # create jellyfin user if it does not exist
if [[ -z "$(getent passwd jellyfin)" ]]; then if [[ -z "$(getent passwd ${JELLYFIN_USER})" ]]; then
adduser --system --ingroup jellyfin --shell /bin/false jellyfin --no-create-home --home ${PROGRAMDATA} \ adduser --system --ingroup ${JELLYFIN_USER} --shell /bin/false ${JELLYFIN_USER} --no-create-home --home ${PROGRAMDATA} \
--gecos "Jellyfin default user" > /dev/null 2>&1 --gecos "Jellyfin default user" > /dev/null 2>&1
fi fi
# ensure $PROGRAMDATA exists # ensure $PROGRAMDATA exists
@ -43,7 +45,7 @@ case "$1" in
mkdir $CACHEDATA mkdir $CACHEDATA
fi fi
# Ensure permissions are correct on all config directories # Ensure permissions are correct on all config directories
chown -R jellyfin $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chown -R ${JELLYFIN_USER} $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA
chgrp adm $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chgrp adm $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA
chmod 0750 $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA chmod 0750 $PROGRAMDATA $CONFIGDATA $LOGDATA $CACHEDATA

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -15,7 +15,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

@ -16,7 +16,7 @@ RUN apt-get update \
# Install dotnet repository # Install dotnet repository
# https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current # https://dotnet.microsoft.com/download/linux-package-manager/debian9/sdk-current
RUN wget https://download.visualstudio.microsoft.com/download/pr/5f0f07ab-cd9a-4498-a9f7-67d90d582180/2a3db6698751e6cbb93ec244cb81cc5f/dotnet-sdk-5.0.202-linux-x64.tar.gz -O dotnet-sdk.tar.gz \ RUN wget https://download.visualstudio.microsoft.com/download/pr/ef13f9da-46dc-4de9-a05e-5a4c20574189/be95913ebf1fb6c66833ca40060d3f65/dotnet-sdk-5.0.203-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \ && mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \ && tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet && ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet

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

Loading…
Cancel
Save