merge branch 'master' into auto-manifest

This commit is contained in:
dkanada 2021-02-23 19:19:38 +09:00
commit bc746b4d05
216 changed files with 3449 additions and 3118 deletions

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1602
namespace Emby.Dlna.ContentDirectory namespace Emby.Dlna.ContentDirectory
{ {

View File

@ -553,7 +553,7 @@ namespace Emby.Dlna
private void DumpProfiles() private void DumpProfiles()
{ {
DeviceProfile[] list = new [] DeviceProfile[] list = new[]
{ {
new SamsungSmartTvProfile(), new SamsungSmartTvProfile(),
new XboxOneProfile(), new XboxOneProfile(),

View File

@ -228,7 +228,10 @@ namespace Emby.Dlna.Main
{ {
try try
{ {
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer); if (communicationsServer != null)
{
((DeviceDiscovery)_deviceDiscovery).Start(communicationsServer);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

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

View File

@ -777,7 +777,7 @@ namespace Emby.Dlna.PlayTo
var currentWait = 0; var currentWait = 0;
while (_device.TransportState != TransportState.Playing && currentWait < MaxWait) while (_device.TransportState != TransportState.Playing && currentWait < MaxWait)
{ {
await Task.Delay(Interval).ConfigureAwait(false); await Task.Delay(Interval, cancellationToken).ConfigureAwait(false);
currentWait += Interval; currentWait += Interval;
} }

View File

@ -1,5 +1,4 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
#pragma warning disable SA1602
namespace Emby.Dlna.PlayTo namespace Emby.Dlna.PlayTo
{ {

View File

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

View File

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

View File

@ -374,7 +374,7 @@ namespace Emby.Server.Implementations
/// <summary> /// <summary>
/// Creates an instance of type and resolves all constructor dependencies. /// Creates an instance of type and resolves all constructor dependencies.
/// </summary> /// </summary>
/// /// <typeparam name="T">The type.</typeparam> /// <typeparam name="T">The type.</typeparam>
/// <returns>T.</returns> /// <returns>T.</returns>
public T CreateInstance<T>() public T CreateInstance<T>()
=> ActivatorUtilities.CreateInstance<T>(ServiceProvider); => ActivatorUtilities.CreateInstance<T>(ServiceProvider);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
#nullable enable
using System.Net.Sockets; using System.Net.Sockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -29,7 +31,7 @@ namespace Emby.Server.Implementations.EntryPoints
/// <summary> /// <summary>
/// The UDP server. /// The UDP server.
/// </summary> /// </summary>
private UdpServer _udpServer; private UdpServer? _udpServer;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed = false; private bool _disposed = false;
@ -71,9 +73,8 @@ namespace Emby.Server.Implementations.EntryPoints
} }
_cancellationTokenSource.Cancel(); _cancellationTokenSource.Cancel();
_udpServer.Dispose();
_cancellationTokenSource.Dispose(); _cancellationTokenSource.Dispose();
_cancellationTokenSource = null; _udpServer?.Dispose();
_udpServer = null; _udpServer = null;
_disposed = true; _disposed = true;

View File

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

View File

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

View File

@ -335,11 +335,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return new Uri(url).AbsoluteUri.TrimEnd('/'); return new Uri(url).AbsoluteUri.TrimEnd('/');
} }
protected EncodingOptions GetEncodingOptions()
{
return Config.GetConfiguration<EncodingOptions>("encoding");
}
private static string GetHdHrIdFromChannelId(string channelId) private static string GetHdHrIdFromChannelId(string channelId)
{ {
return channelId.Split('_')[1]; return channelId.Split('_')[1];

View File

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

View File

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

View File

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

View File

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

View File

@ -4,11 +4,11 @@
"VersionNumber": "Верзија {0}", "VersionNumber": "Верзија {0}",
"ValueSpecialEpisodeName": "Специјал - {0}", "ValueSpecialEpisodeName": "Специјал - {0}",
"ValueHasBeenAddedToLibrary": "{0} је додато у вашу медијску библиотеку", "ValueHasBeenAddedToLibrary": "{0} је додато у вашу медијску библиотеку",
"UserStoppedPlayingItemWithValues": "{0} заврши пуштање {1} на {2}", "UserStoppedPlayingItemWithValues": "{0} завршио пуштање {1} на {2}",
"UserStartedPlayingItemWithValues": "{0} пушта {1} на {2}", "UserStartedPlayingItemWithValues": "{0} пушта {1} на {2}",
"UserPasswordChangedWithName": "Лозинка је промењена за корисника {0}", "UserPasswordChangedWithName": "Лозинка је промењена за корисника {0}",
"UserOnlineFromDevice": "{0} је на вези од {1}", "UserOnlineFromDevice": "{0} је на вези од {1}",
"UserOfflineFromDevice": "{0} се одвезао са {1}", "UserOfflineFromDevice": "{0} је прекинуо/а везу са {1}",
"UserLockedOutWithName": "Корисник {0} је закључан", "UserLockedOutWithName": "Корисник {0} је закључан",
"UserDownloadingItemWithValues": "{0} преузима {1}", "UserDownloadingItemWithValues": "{0} преузима {1}",
"UserDeletedWithName": "Корисник {0} је обрисан", "UserDeletedWithName": "Корисник {0} је обрисан",
@ -41,7 +41,7 @@
"NotificationOptionPluginError": "Грешка прикључка", "NotificationOptionPluginError": "Грешка прикључка",
"NotificationOptionNewLibraryContent": "Додат нови садржај", "NotificationOptionNewLibraryContent": "Додат нови садржај",
"NotificationOptionInstallationFailed": "Неуспела инсталација", "NotificationOptionInstallationFailed": "Неуспела инсталација",
"NotificationOptionCameraImageUploaded": "Слика са камере послата", "NotificationOptionCameraImageUploaded": "Слика са камере отпремљена",
"NotificationOptionAudioPlaybackStopped": "Заустављено пуштање звука", "NotificationOptionAudioPlaybackStopped": "Заустављено пуштање звука",
"NotificationOptionAudioPlayback": "Покренуто пуштање звука", "NotificationOptionAudioPlayback": "Покренуто пуштање звука",
"NotificationOptionApplicationUpdateInstalled": "Ажурирање инсталирано", "NotificationOptionApplicationUpdateInstalled": "Ажурирање инсталирано",
@ -86,7 +86,7 @@
"Channels": "Канали", "Channels": "Канали",
"CameraImageUploadedFrom": "Нова фотографија је учитана са {0}", "CameraImageUploadedFrom": "Нова фотографија је учитана са {0}",
"Books": "Књиге", "Books": "Књиге",
"AuthenticationSucceededWithUserName": "{0} успешно проверено", "AuthenticationSucceededWithUserName": "{0} Успешна аутентикација",
"Artists": "Извођачи", "Artists": "Извођачи",
"Application": "Апликација", "Application": "Апликација",
"AppDeviceValues": "Апликација: {0}, Уређај: {1}", "AppDeviceValues": "Апликација: {0}, Уређај: {1}",
@ -100,7 +100,7 @@
"TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.", "TaskUpdatePluginsDescription": "Преузима и инсталира исправке за додатке који су конфигурисани за аутоматско ажурирање.",
"TaskUpdatePlugins": "Ажурирајте додатке", "TaskUpdatePlugins": "Ажурирајте додатке",
"TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.", "TaskRefreshPeopleDescription": "Ажурира метаподатке за глумце и редитеље у вашој медијској библиотеци.",
"TaskRefreshPeople": "Освежите људе", "TaskRefreshPeople": "Освежите кориснике",
"TaskCleanLogsDescription": "Брише логове старије од {0} дана.", "TaskCleanLogsDescription": "Брише логове старије од {0} дана.",
"TaskCleanLogs": "Очистите директоријум логова", "TaskCleanLogs": "Очистите директоријум логова",
"TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.", "TaskRefreshLibraryDescription": "Скенира вашу медијску библиотеку за нове датотеке и освежава метаподатке.",
@ -116,6 +116,6 @@
"TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.", "TaskCleanActivityLogDescription": "Брише историју активности старију од конфигурисаног броја година.",
"TaskCleanActivityLog": "Очисти историју активности", "TaskCleanActivityLog": "Очисти историју активности",
"Undefined": "Недефинисано", "Undefined": "Недефинисано",
"Forced": "Форсирано", "Forced": "Принудно",
"Default": "Подразумевано" "Default": "Подразумевано"
} }

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;

View File

@ -6,7 +6,6 @@ using System.Net.Mime;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Models; using Jellyfin.Api.Models;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
using MediaBrowser.Model.Net; using MediaBrowser.Model.Net;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -22,22 +21,18 @@ namespace Jellyfin.Api.Controllers
public class DashboardController : BaseJellyfinApiController public class DashboardController : BaseJellyfinApiController
{ {
private readonly ILogger<DashboardController> _logger; private readonly ILogger<DashboardController> _logger;
private readonly IServerApplicationHost _appHost;
private readonly IPluginManager _pluginManager; private readonly IPluginManager _pluginManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class. /// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary> /// </summary>
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param> /// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
/// <param name="pluginManager">Instance of <see cref="IPluginManager"/> interface.</param> /// <param name="pluginManager">Instance of <see cref="IPluginManager"/> interface.</param>
public DashboardController( public DashboardController(
ILogger<DashboardController> logger, ILogger<DashboardController> logger,
IServerApplicationHost appHost,
IPluginManager pluginManager) IPluginManager pluginManager)
{ {
_logger = logger; _logger = logger;
_appHost = appHost;
_pluginManager = pluginManager; _pluginManager = pluginManager;
} }
@ -51,7 +46,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("web/ConfigurationPages")] [HttpGet("web/ConfigurationPages")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ConfigurationPageInfo?>> GetConfigurationPages( public ActionResult<IEnumerable<ConfigurationPageInfo>> GetConfigurationPages(
[FromQuery] bool? enableInMainMenu) [FromQuery] bool? enableInMainMenu)
{ {
var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList(); var configPages = _pluginManager.Plugins.SelectMany(GetConfigPages).ToList();
@ -77,38 +72,22 @@ namespace Jellyfin.Api.Controllers
[ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")] [ProducesFile(MediaTypeNames.Text.Html, "application/x-javascript")]
public ActionResult GetDashboardConfigurationPage([FromQuery] string? name) public ActionResult GetDashboardConfigurationPage([FromQuery] string? name)
{ {
IPlugin? plugin = null;
Stream? stream = null;
var isJs = false;
var isTemplate = false;
var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase)); var altPage = GetPluginPages().FirstOrDefault(p => string.Equals(p.Item1.Name, name, StringComparison.OrdinalIgnoreCase));
if (altPage != null) if (altPage == null)
{ {
plugin = altPage.Item2; return NotFound();
stream = plugin.GetType().Assembly.GetManifestResourceStream(altPage.Item1.EmbeddedResourcePath);
isJs = string.Equals(Path.GetExtension(altPage.Item1.EmbeddedResourcePath), ".js", StringComparison.OrdinalIgnoreCase);
isTemplate = altPage.Item1.EmbeddedResourcePath.EndsWith(".template.html", StringComparison.Ordinal);
} }
if (plugin != null && stream != null) IPlugin plugin = altPage.Item2;
string resourcePath = altPage.Item1.EmbeddedResourcePath;
Stream? stream = plugin.GetType().Assembly.GetManifestResourceStream(resourcePath);
if (stream == null)
{ {
if (isJs) _logger.LogError("Failed to get resource {Resource} from plugin {Plugin}", resourcePath, plugin.Name);
{ return NotFound();
return File(stream, MimeTypes.GetMimeType("page.js"));
}
if (isTemplate)
{
return File(stream, MimeTypes.GetMimeType("page.html"));
}
return File(stream, MimeTypes.GetMimeType("page.html"));
} }
return NotFound(); return File(stream, MimeTypes.GetMimeType(resourcePath));
} }
private IEnumerable<ConfigurationPageInfo> GetConfigPages(LocalPlugin plugin) private IEnumerable<ConfigurationPageInfo> GetConfigPages(LocalPlugin plugin)
@ -120,7 +99,7 @@ namespace Jellyfin.Api.Controllers
{ {
if (plugin?.Instance is not IHasWebPages hasWebPages) if (plugin?.Instance is not IHasWebPages hasWebPages)
{ {
return new List<Tuple<PluginPageInfo, IPlugin>>(); return Enumerable.Empty<Tuple<PluginPageInfo, IPlugin>>();
} }
return hasWebPages.GetPages().Select(i => new Tuple<PluginPageInfo, IPlugin>(i, plugin.Instance)); return hasWebPages.GetPages().Select(i => new Tuple<PluginPageInfo, IPlugin>(i, plugin.Instance));

View File

@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
} }
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))); user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
await _providerManager await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path) .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
} }
user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType))); user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
await _providerManager await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path) .SaveImage(memoryStream, mimeType, user.ProfileImage.Path)

View File

@ -298,9 +298,7 @@ namespace Jellyfin.Api.Controllers
} }
var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty); var imagePath = Path.Combine(plugin.Path, plugin.Manifest.ImagePath ?? string.Empty);
if (((ServerConfiguration)_config.CommonConfiguration).DisablePluginImages if (plugin.Manifest.ImagePath == null || !System.IO.File.Exists(imagePath))
|| plugin.Manifest.ImagePath == null
|| !System.IO.File.Exists(imagePath))
{ {
return NotFound(); return NotFound();
} }

View File

@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers
DeInterlace = true, DeInterlace = true,
RequireNonAnamorphic = true, RequireNonAnamorphic = true,
EnableMpegtsM2TsMode = true, EnableMpegtsM2TsMode = true,
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
Context = EncodingContext.Static, Context = EncodingContext.Static,
StreamOptions = new Dictionary<string, string>(), StreamOptions = new Dictionary<string, string>(),
EnableAdaptiveBitrateStreaming = true EnableAdaptiveBitrateStreaming = true
@ -254,7 +254,7 @@ namespace Jellyfin.Api.Controllers
CopyTimestamps = true, CopyTimestamps = true,
StartTimeTicks = startTimeTicks, StartTimeTicks = startTimeTicks,
SubtitleMethod = SubtitleDeliveryMethod.Embed, SubtitleMethod = SubtitleDeliveryMethod.Embed,
TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(",", mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()), TranscodeReasons = mediaSource.TranscodeReasons == null ? null : string.Join(',', mediaSource.TranscodeReasons.Select(i => i.ToString()).ToArray()),
Context = EncodingContext.Static Context = EncodingContext.Static
}; };

View File

@ -113,14 +113,5 @@ namespace Jellyfin.Api.Extensions
return dtoOptions; return dtoOptions;
} }
/// <summary>
/// Check if DtoOptions contains field.
/// </summary>
/// <param name="dtoOptions">DtoOptions object.</param>
/// <param name="field">Field to check.</param>
/// <returns>Field existence.</returns>
internal static bool ContainsField(this DtoOptions dtoOptions, ItemFields field)
=> dtoOptions.Fields != null && dtoOptions.Fields.Contains(field);
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Mime;
using System.Security.Claims; using System.Security.Claims;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
@ -171,13 +172,15 @@ namespace Jellyfin.Api.Helpers
var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString(); var queryString = _httpContextAccessor.HttpContext.Request.QueryString.ToString();
// from universal audio service // from universal audio service
if (queryString.IndexOf("SegmentContainer", StringComparison.OrdinalIgnoreCase) == -1 && !string.IsNullOrWhiteSpace(state.Request.SegmentContainer)) if (!string.IsNullOrWhiteSpace(state.Request.SegmentContainer)
&& !queryString.Contains("SegmentContainer", StringComparison.OrdinalIgnoreCase))
{ {
queryString += "&SegmentContainer=" + state.Request.SegmentContainer; queryString += "&SegmentContainer=" + state.Request.SegmentContainer;
} }
// from universal audio service // from universal audio service
if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons) && queryString.IndexOf("TranscodeReasons=", StringComparison.OrdinalIgnoreCase) == -1) if (!string.IsNullOrWhiteSpace(state.Request.TranscodeReasons)
&& !queryString.Contains("TranscodeReasons=", StringComparison.OrdinalIgnoreCase))
{ {
queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons; queryString += "&TranscodeReasons=" + state.Request.TranscodeReasons;
} }
@ -222,7 +225,7 @@ namespace Jellyfin.Api.Helpers
{ {
// Force HEVC Main Profile and disable video stream copy. // Force HEVC Main Profile and disable video stream copy.
state.OutputVideoCodec = "hevc"; state.OutputVideoCodec = "hevc";
var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(",", requestedVideoProfiles), "main"); var sdrVideoUrl = ReplaceProfile(playlistUrl, "hevc", string.Join(',', requestedVideoProfiles), "main");
sdrVideoUrl += "&AllowVideoStreamCopy=false"; sdrVideoUrl += "&AllowVideoStreamCopy=false";
EncodingHelper encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration); EncodingHelper encodingHelper = new EncodingHelper(_mediaEncoder, _fileSystem, _subtitleEncoder, _configuration);
@ -560,13 +563,13 @@ namespace Jellyfin.Api.Helpers
profileString = state.GetRequestedProfiles(codec).FirstOrDefault() ?? string.Empty; profileString = state.GetRequestedProfiles(codec).FirstOrDefault() ?? string.Empty;
if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase)) if (string.Equals(state.ActualOutputVideoCodec, "h264", StringComparison.OrdinalIgnoreCase))
{ {
profileString = profileString ?? "high"; profileString ??= "high";
} }
if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase) if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)) || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase))
{ {
profileString = profileString ?? "main"; profileString ??= "main";
} }
} }

View File

@ -39,7 +39,7 @@ namespace Jellyfin.Api.Helpers
} }
// Can't dispose the response as it's required up the call chain. // Can't dispose the response as it's required up the call chain.
var response = await httpClient.GetAsync(new Uri(state.MediaPath)).ConfigureAwait(false); var response = await httpClient.GetAsync(new Uri(state.MediaPath), cancellationToken).ConfigureAwait(false);
var contentType = response.Content.Headers.ContentType?.ToString(); var contentType = response.Content.Headers.ContentType?.ToString();
httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none"; httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";

View File

@ -523,7 +523,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="type">Dlna profile type.</param> /// <param name="type">Dlna profile type.</param>
public void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type) public void NormalizeMediaSourceContainer(MediaSourceInfo mediaSource, DeviceProfile profile, DlnaProfileType type)
{ {
mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, mediaSource.Path, profile, type); mediaSource.Container = StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(mediaSource.Container, profile, type);
} }
private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken) private void SetDeviceSpecificSubtitleInfo(StreamInfo info, MediaSourceInfo mediaSource, string accessToken)

View File

@ -183,7 +183,7 @@ namespace Jellyfin.Api.Helpers
if (string.IsNullOrEmpty(containerInternal)) if (string.IsNullOrEmpty(containerInternal))
{ {
containerInternal = streamingRequest.Static ? containerInternal = streamingRequest.Static ?
StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, state.MediaPath, null, DlnaProfileType.Audio) StreamBuilder.NormalizeMediaSourceFormatIntoSingleContainer(state.InputContainer, null, DlnaProfileType.Audio)
: GetOutputFileExtension(state); : GetOutputFileExtension(state);
} }
@ -245,7 +245,7 @@ namespace Jellyfin.Api.Helpers
var ext = string.IsNullOrWhiteSpace(state.OutputContainer) var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state) ? GetOutputFileExtension(state)
: ('.' + state.OutputContainer); : ("." + state.OutputContainer);
state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId); state.OutputFilePath = GetOutputFilePath(state, ext!, serverConfigurationManager, streamingRequest.DeviceId, streamingRequest.PlaySessionId);

View File

@ -17,8 +17,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="5.0.3" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.0.7" />
<PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.2" /> <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.0.7" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -38,4 +38,10 @@
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet> <CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>Jellyfin.Api.Tests</_Parameter1>
</AssemblyAttribute>
</ItemGroup>
</Project> </Project>

View File

@ -1,6 +1,5 @@
using System; using System;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
namespace Jellyfin.Api.Models namespace Jellyfin.Api.Models
@ -25,6 +24,14 @@ namespace Jellyfin.Api.Models
PluginId = plugin?.Id; PluginId = plugin?.Id;
} }
/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationPageInfo"/> class.
/// </summary>
public ConfigurationPageInfo()
{
Name = string.Empty;
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>

View File

@ -98,7 +98,7 @@ namespace Jellyfin.Api.Models.PlaybackDtos
private EncodingOptions GetOptions() private EncodingOptions GetOptions()
{ {
return _config.GetConfiguration<EncodingOptions>("encoding"); return _config.GetEncodingOptions();
} }
private async void TimerCallback(object? state) private async void TimerCallback(object? state)

View File

@ -0,0 +1,23 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Jellyfin.Server.Implementations")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Jellyfin Project")]
[assembly: AssemblyProduct("Jellyfin Server")]
[assembly: AssemblyCopyright("Copyright © 2019 Jellyfin Contributors. Code released under the GNU General Public License")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
[assembly: InternalsVisibleTo("Jellyfin.Server.Implementations.Tests")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]

View File

@ -137,17 +137,14 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentNullException(nameof(user)); throw new ArgumentNullException(nameof(user));
} }
if (string.IsNullOrWhiteSpace(newName)) ThrowIfInvalidUsername(newName);
{
throw new ArgumentException("Invalid username", nameof(newName));
}
if (user.Username.Equals(newName, StringComparison.Ordinal)) if (user.Username.Equals(newName, StringComparison.Ordinal))
{ {
throw new ArgumentException("The new and old names must be different."); throw new ArgumentException("The new and old names must be different.");
} }
if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.Ordinal))) if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase)))
{ {
throw new ArgumentException(string.Format( throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@ -201,9 +198,14 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/> /// <inheritdoc/>
public async Task<User> CreateUserAsync(string name) public async Task<User> CreateUserAsync(string name)
{ {
if (!IsValidUsername(name)) ThrowIfInvalidUsername(name);
if (Users.Any(u => u.Username.Equals(name, StringComparison.OrdinalIgnoreCase)))
{ {
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)"); throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
"A user with the name '{0}' already exists.",
name));
} }
await using var dbContext = _dbProvider.CreateContext(); await using var dbContext = _dbProvider.CreateContext();
@ -725,12 +727,22 @@ namespace Jellyfin.Server.Implementations.Users
_users[user.Id] = user; _users[user.Id] = user;
} }
internal static void ThrowIfInvalidUsername(string name)
{
if (!string.IsNullOrWhiteSpace(name) && IsValidUsername(name))
{
return;
}
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)", nameof(name));
}
private static bool IsValidUsername(string name) private static bool IsValidUsername(string name)
{ {
// This is some regex that matches only on unicode "word" characters, as well as -, _ and @ // This is some regex that matches only on unicode "word" characters, as well as -, _ and @
// In theory this will cut out most if not all 'control' characters which should help minimize any weirdness // In theory this will cut out most if not all 'control' characters which should help minimize any weirdness
// Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( ) // Usernames can contain letters (a-z + whatever else unicode is cool with), numbers (0-9), at-signs (@), dashes (-), underscores (_), apostrophes ('), periods (.) and spaces ( )
return Regex.IsMatch(name, @"^[\w\ \-'._@]*$"); return Regex.IsMatch(name, @"^[\w\ \-'._@]+$");
} }
private IAuthenticationProvider GetAuthenticationProvider(User user) private IAuthenticationProvider GetAuthenticationProvider(User user)

View File

@ -11,7 +11,6 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity; using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Events; using Jellyfin.Server.Implementations.Events;
using Jellyfin.Server.Implementations.Users; using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.BaseItemManager; using MediaBrowser.Controller.BaseItemManager;
using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Drawing;

View File

@ -310,6 +310,7 @@ namespace Jellyfin.Server.Extensions
// Allow parameters to properly be nullable. // Allow parameters to properly be nullable.
c.UseAllOfToExtendReferenceSchemas(); c.UseAllOfToExtendReferenceSchemas();
c.SupportNonNullableReferenceTypes();
// TODO - remove when all types are supported in System.Text.Json // TODO - remove when all types are supported in System.Text.Json
c.AddSwaggerTypeMappings(); c.AddSwaggerTypeMappings();

View File

@ -32,7 +32,7 @@ namespace Jellyfin.Server.Migrations.Routines
public void Perform() public void Perform()
{ {
// Set EnableThrottling to false since it wasn't used before and may introduce issues // Set EnableThrottling to false since it wasn't used before and may introduce issues
var encoding = _configManager.GetConfiguration<EncodingOptions>("encoding"); var encoding = _configManager.GetEncodingOptions();
if (encoding.EnableThrottling) if (encoding.EnableThrottling)
{ {
_logger.LogInformation("Disabling transcoding throttling during migration"); _logger.LogInformation("Disabling transcoding throttling during migration");

View File

@ -74,6 +74,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Model.Tests", "tests\Jellyfin.Model.Tests\Jellyfin.Model.Tests.csproj", "{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -200,6 +202,10 @@ Global
{30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU {30922383-D513-4F4D-B890-A940B57FA353}.Release|Any CPU.Build.0 = Release|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -214,6 +220,7 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6} {30922383-D513-4F4D-B890-A940B57FA353} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {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}

View File

@ -0,0 +1,51 @@
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
/// Class BaseExtensions.
/// </summary>
public static class StreamExtensions
{
/// <summary>
/// Reads all lines in the <see cref="Stream" />.
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read from.</param>
/// <returns>All lines in the stream.</returns>
public static string[] ReadAllLines(this Stream stream)
=> ReadAllLines(stream, Encoding.UTF8);
/// <summary>
/// Reads all lines in the <see cref="Stream" />.
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read from.</param>
/// <param name="encoding">The character encoding to use.</param>
/// <returns>All lines in the stream.</returns>
public static string[] ReadAllLines(this Stream stream, Encoding encoding)
{
using (StreamReader reader = new StreamReader(stream, encoding))
{
return ReadAllLines(reader).ToArray();
}
}
/// <summary>
/// Reads all lines in the <see cref="StreamReader" />.
/// </summary>
/// <param name="reader">The <see cref="StreamReader" /> to read from.</param>
/// <returns>All lines in the stream.</returns>
public static IEnumerable<string> ReadAllLines(this StreamReader reader)
{
string? line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
}

View File

@ -1,37 +0,0 @@
#nullable enable
using System;
namespace MediaBrowser.Common.Extensions
{
/// <summary>
/// Extensions methods to simplify string operations.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Returns the part on the left of the <c>needle</c>.
/// </summary>
/// <param name="haystack">The string to seek.</param>
/// <param name="needle">The needle to find.</param>
/// <returns>The part left of the <paramref name="needle" />.</returns>
public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, char needle)
{
var pos = haystack.IndexOf(needle);
return pos == -1 ? haystack : haystack[..pos];
}
/// <summary>
/// Returns the part on the left of the <c>needle</c>.
/// </summary>
/// <param name="haystack">The string to seek.</param>
/// <param name="needle">The needle to find.</param>
/// <param name="stringComparison">One of the enumeration values that specifies the rules for the search.</param>
/// <returns>The part left of the <c>needle</c>.</returns>
public static ReadOnlySpan<char> LeftPart(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison stringComparison = default)
{
var pos = haystack.IndexOf(needle, stringComparison);
return pos == -1 ? haystack : haystack[..pos];
}
}
}

View File

@ -31,7 +31,6 @@ namespace MediaBrowser.Common.Json
WriteIndented = false, WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
NumberHandling = JsonNumberHandling.AllowReadingFromString, NumberHandling = JsonNumberHandling.AllowReadingFromString,
PropertyNameCaseInsensitive = true,
Converters = Converters =
{ {
new JsonGuidConverter(), new JsonGuidConverter(),

View File

@ -1,11 +1,6 @@
#pragma warning disable CA1062 // Validate arguments of public methods
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Net; using System.Net;
using System.Runtime.CompilerServices;
using System.Text;
namespace MediaBrowser.Common.Net namespace MediaBrowser.Common.Net
{ {

View File

@ -1,10 +1,7 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Common.Plugins namespace MediaBrowser.Common.Plugins
{ {

View File

@ -1243,7 +1243,7 @@ namespace MediaBrowser.Controller.Entities
} }
} }
return string.Join("/", terms.ToArray()); return string.Join('/', terms.ToArray());
} }
/// <summary> /// <summary>
@ -2795,7 +2795,7 @@ namespace MediaBrowser.Controller.Entities
{ {
var list = GetEtagValues(user); var list = GetEtagValues(user);
return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture); return string.Join('|', list).GetMD5().ToString("N", CultureInfo.InvariantCulture);
} }
protected virtual List<string> GetEtagValues(User user) protected virtual List<string> GetEtagValues(User user)

View File

@ -107,7 +107,7 @@ namespace MediaBrowser.Controller.Entities.TV
return key; return key;
} }
return key + "-" + string.Join("-", folders); return key + "-" + string.Join('-', folders);
} }
private static string GetUniqueSeriesKey(BaseItem series) private static string GetUniqueSeriesKey(BaseItem series)

View File

@ -131,6 +131,12 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options) private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
{ {
var videoStream = state.VideoStream; var videoStream = state.VideoStream;
if (videoStream == null)
{
// Remote stream doesn't have media info, disable vpp tonemapping.
return false;
}
var codec = videoStream.Codec; var codec = videoStream.Codec;
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)) if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{ {
@ -592,7 +598,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.IsVideoRequest if (state.IsVideoRequest
&& ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && ((string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
&& (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder)) && (isNvdecDecoder || isCuvidHevcDecoder || isSwDecoder))
|| (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) || (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
&& (isD3d11vaDecoder || isSwDecoder)))) && (isD3d11vaDecoder || isSwDecoder))))
{ {
if (isTonemappingSupported) if (isTonemappingSupported)
@ -1381,7 +1387,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedProfile = requestedProfiles[0]; var requestedProfile = requestedProfiles[0];
// strip spaces because they may be stripped out on the query string as well // strip spaces because they may be stripped out on the query string as well
if (!string.IsNullOrEmpty(videoStream.Profile) && !requestedProfiles.Contains(videoStream.Profile.Replace(" ", ""), StringComparer.OrdinalIgnoreCase)) if (!string.IsNullOrEmpty(videoStream.Profile)
&& !requestedProfiles.Contains(videoStream.Profile.Replace(" ", "", StringComparison.Ordinal), StringComparer.OrdinalIgnoreCase))
{ {
var currentScore = GetVideoProfileScore(videoStream.Profile); var currentScore = GetVideoProfileScore(videoStream.Profile);
var requestedScore = GetVideoProfileScore(requestedProfile); var requestedScore = GetVideoProfileScore(requestedProfile);
@ -1710,7 +1717,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (filters.Count > 0) if (filters.Count > 0)
{ {
return " -af \"" + string.Join(",", filters) + "\""; return " -af \"" + string.Join(',', filters) + "\"";
} }
return string.Empty; return string.Empty;
@ -2530,7 +2537,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
// If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30; var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.AverageFrameRate ?? 60) <= 30;
var isScalingInAdvance = false; var isScalingInAdvance = false;
var isCudaDeintInAdvance = false; var isCudaDeintInAdvance = false;
@ -2888,7 +2895,7 @@ namespace MediaBrowser.Controller.MediaEncoding
output += string.Format( output += string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"{0}", "{0}",
string.Join(",", filters)); string.Join(',', filters));
} }
return output; return output;
@ -2914,7 +2921,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (threads <= 0) if (threads <= 0)
{ {
return 0; return 0;
} }
else if (threads >= Environment.ProcessorCount) else if (threads >= Environment.ProcessorCount)
{ {
return Environment.ProcessorCount; return Environment.ProcessorCount;
@ -3080,7 +3087,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{ {
inputModifier += " -deint 1"; inputModifier += " -deint 1";
if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.RealFrameRate ?? 60) > 30) if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.AverageFrameRate ?? 60) > 30)
{ {
inputModifier += " -drop_second_field 1"; inputModifier += " -drop_second_field 1";
} }
@ -3864,7 +3871,7 @@ namespace MediaBrowser.Controller.MediaEncoding
GetInputArgument(state, encodingOptions), GetInputArgument(state, encodingOptions),
threads, threads,
" -vn", " -vn",
string.Join(" ", audioTranscodeParams), string.Join(' ', audioTranscodeParams),
outputPath, outputPath,
string.Empty, string.Empty,
string.Empty, string.Empty,

View File

@ -214,7 +214,7 @@ namespace MediaBrowser.LocalMetadata.Savers
if (item.LockedFields.Length > 0) if (item.LockedFields.Length > 0)
{ {
writer.WriteElementString("LockedFields", string.Join("|", item.LockedFields)); writer.WriteElementString("LockedFields", string.Join('|', item.LockedFields));
} }
if (item.CriticRating.HasValue) if (item.CriticRating.HasValue)

View File

@ -103,7 +103,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath() public void SetFFmpegPath()
{ {
// 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence // 1) Custom path stored in config/encoding xml file under tag <EncoderAppPath> takes precedence
if (!ValidatePath(_configurationManager.GetConfiguration<EncodingOptions>("encoding").EncoderAppPath, FFmpegLocation.Custom)) if (!ValidatePath(_configurationManager.GetEncodingOptions().EncoderAppPath, FFmpegLocation.Custom))
{ {
// 2) Check if the --ffmpeg CLI switch has been given // 2) Check if the --ffmpeg CLI switch has been given
if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument)) if (!ValidatePath(_startupOptionFFmpegPath, FFmpegLocation.SetByArgument))
@ -118,7 +118,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
} }
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI // Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding"); var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty; config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
_configurationManager.SaveConfiguration("encoding", config); _configurationManager.SaveConfiguration("encoding", config);
@ -177,7 +177,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Write the new ffmpeg path to the xml as <EncoderAppPath> // Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup // This ensures its not lost on next startup
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding"); var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPath = newPath; config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config); _configurationManager.SaveConfiguration("encoding", config);

View File

@ -24,6 +24,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" /> <PackageReference Include="BDInfo" Version="0.7.6.1" />
<PackageReference Include="libse" Version="3.5.8" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" /> <PackageReference Include="UTF.Unknown" Version="2.3.0" />

View File

@ -681,9 +681,9 @@ namespace MediaBrowser.MediaEncoding.Probing
{ {
stream.Type = MediaStreamType.Subtitle; stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec); stream.Codec = NormalizeSubtitleCodec(stream.Codec);
stream.localizedUndefined = _localization.GetLocalizedString("Undefined"); stream.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
stream.localizedDefault = _localization.GetLocalizedString("Default"); stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.localizedForced = _localization.GetLocalizedString("Forced"); stream.LocalizedForced = _localization.GetLocalizedString("Forced");
} }
else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase)) else if (string.Equals(streamInfo.CodecType, "video", StringComparison.OrdinalIgnoreCase))
{ {
@ -1496,7 +1496,7 @@ namespace MediaBrowser.MediaEncoding.Probing
video.IndexNumber = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[0], CultureInfo.InvariantCulture); video.IndexNumber = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[0], CultureInfo.InvariantCulture);
int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[1], CultureInfo.InvariantCulture); int totalEpisodesInSeason = int.Parse(numbers[0].Replace(".", string.Empty, StringComparison.Ordinal).Split('/')[1], CultureInfo.InvariantCulture);
description = string.Join(" ", numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it description = string.Join(' ', numbers, 1, numbers.Length - 1).Trim(); // Skip the first, concatenate the rest, clean up spaces and save it
} }
else else
{ {
@ -1508,7 +1508,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (subtitle.Contains('.', StringComparison.Ordinal)) if (subtitle.Contains('.', StringComparison.Ordinal))
{ {
// skip the comment, keep the subtitle // skip the comment, keep the subtitle
description = string.Join(".", subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first description = string.Join('.', subtitle.Split('.'), 1, subtitle.Split('.').Length - 1).Trim(); // skip the first
} }
else else
{ {

View File

@ -1,130 +1,21 @@
#pragma warning disable CS1591 #nullable enable
using System; using Microsoft.Extensions.Logging;
using System.Collections.Generic; using Nikse.SubtitleEdit.Core.SubtitleFormats;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
public class AssParser : ISubtitleParser /// <summary>
/// Advanced SubStation Alpha subtitle parser.
/// </summary>
public class AssParser : SubtitleEditParser<AdvancedSubStationAlpha>
{ {
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// <summary>
/// Initializes a new instance of the <see cref="AssParser"/> class.
/// <inheritdoc /> /// </summary>
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) /// <param name="logger">The logger.</param>
public AssParser(ILogger logger) : base(logger)
{ {
var trackInfo = new SubtitleTrackInfo();
var trackEvents = new List<SubtitleTrackEvent>();
var eventIndex = 1;
using (var reader = new StreamReader(stream))
{
string line;
while (!string.Equals(reader.ReadLine(), "[Events]", StringComparison.Ordinal))
{
}
var headers = ParseFieldHeaders(reader.ReadLine());
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line[0] == '[')
{
break;
}
var subEvent = new SubtitleTrackEvent { Id = eventIndex.ToString(_usCulture) };
eventIndex++;
const string Dialogue = "Dialogue: ";
var sections = line.Substring(Dialogue.Length).Split(',');
subEvent.StartPositionTicks = GetTicks(sections[headers["Start"]]);
subEvent.EndPositionTicks = GetTicks(sections[headers["End"]]);
subEvent.Text = string.Join(',', sections[headers["Text"]..]);
RemoteNativeFormatting(subEvent);
subEvent.Text = subEvent.Text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(\\[\w]+\(?([\w0-9]+,?)+\)?)+\}", string.Empty, RegexOptions.IgnoreCase);
trackEvents.Add(subEvent);
}
}
trackInfo.TrackEvents = trackEvents;
return trackInfo;
}
private long GetTicks(ReadOnlySpan<char> time)
{
return TimeSpan.TryParseExact(time, @"h\:mm\:ss\.ff", _usCulture, out var span)
? span.Ticks : 0;
}
internal static Dictionary<string, int> ParseFieldHeaders(string line)
{
const string Format = "Format: ";
var fields = line.Substring(Format.Length).Split(',').Select(x => x.Trim()).ToList();
return new Dictionary<string, int>
{
{ "Start", fields.IndexOf("Start") },
{ "End", fields.IndexOf("End") },
{ "Text", fields.IndexOf("Text") }
};
}
private void RemoteNativeFormatting(SubtitleTrackEvent p)
{
int indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
string pre = string.Empty;
while (indexOfBegin >= 0 && p.Text.IndexOf('}', StringComparison.Ordinal) > indexOfBegin)
{
string s = p.Text.Substring(indexOfBegin);
if (s.StartsWith("{\\an1}", StringComparison.Ordinal) ||
s.StartsWith("{\\an2}", StringComparison.Ordinal) ||
s.StartsWith("{\\an3}", StringComparison.Ordinal) ||
s.StartsWith("{\\an4}", StringComparison.Ordinal) ||
s.StartsWith("{\\an5}", StringComparison.Ordinal) ||
s.StartsWith("{\\an6}", StringComparison.Ordinal) ||
s.StartsWith("{\\an7}", StringComparison.Ordinal) ||
s.StartsWith("{\\an8}", StringComparison.Ordinal) ||
s.StartsWith("{\\an9}", StringComparison.Ordinal))
{
pre = s.Substring(0, 6);
}
else if (s.StartsWith("{\\an1\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an2\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an3\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an4\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an5\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an6\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an7\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an8\\", StringComparison.Ordinal) ||
s.StartsWith("{\\an9\\", StringComparison.Ordinal))
{
pre = s.Substring(0, 5) + "}";
}
int indexOfEnd = p.Text.IndexOf('}', StringComparison.Ordinal);
p.Text = p.Text.Remove(indexOfBegin, (indexOfEnd - indexOfBegin) + 1);
indexOfBegin = p.Text.IndexOf('{', StringComparison.Ordinal);
}
p.Text = pre + p.Text;
} }
} }
} }

View File

@ -1,102 +1,21 @@
#pragma warning disable CS1591 #nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
public class SrtParser : ISubtitleParser /// <summary>
/// SubRip subtitle parser.
/// </summary>
public class SrtParser : SubtitleEditParser<SubRip>
{ {
private readonly ILogger _logger; /// <summary>
/// Initializes a new instance of the <see cref="SrtParser"/> class.
private readonly CultureInfo _usCulture = new CultureInfo("en-US"); /// </summary>
/// <param name="logger">The logger.</param>
public SrtParser(ILogger logger) public SrtParser(ILogger logger) : base(logger)
{ {
_logger = logger;
}
/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var trackInfo = new SubtitleTrackInfo();
var trackEvents = new List<SubtitleTrackEvent>();
using (var reader = new StreamReader(stream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
var subEvent = new SubtitleTrackEvent { Id = line };
line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
var time = Regex.Split(line, @"[\t ]*-->[\t ]*");
if (time.Length < 2)
{
// This occurs when subtitle text has an empty line as part of the text.
// Need to adjust the break statement below to resolve this.
_logger.LogWarning("Unrecognized line in srt: {0}", line);
continue;
}
subEvent.StartPositionTicks = GetTicks(time[0]);
var endTime = time[1].AsSpan();
var idx = endTime.IndexOf(' ');
if (idx > 0)
{
endTime = endTime.Slice(0, idx);
}
subEvent.EndPositionTicks = GetTicks(endTime);
var multiline = new List<string>();
while ((line = reader.ReadLine()) != null)
{
if (line.Length == 0)
{
break;
}
multiline.Add(line);
}
subEvent.Text = string.Join(ParserValues.NewLine, multiline);
subEvent.Text = subEvent.Text.Replace(@"\N", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, @"\{(?:\\[0-9]?[\w.-]+(?:\([^\)]*\)|&H?[0-9A-Fa-f]+&|))+\}", string.Empty, RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, "<", "&lt;", RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, ">", "&gt;", RegexOptions.IgnoreCase);
subEvent.Text = Regex.Replace(subEvent.Text, "&lt;(\\/?(font|b|u|i|s))((\\s+(\\w|\\w[\\w\\-]*\\w)(\\s*=\\s*(?:\\\".*?\\\"|'.*?'|[^'\\\">\\s]+))?)+\\s*|\\s*)(\\/?)&gt;", "<$1$3$7>", RegexOptions.IgnoreCase);
trackEvents.Add(subEvent);
}
}
trackInfo.TrackEvents = trackEvents;
return trackInfo;
}
private long GetTicks(ReadOnlySpan<char> time)
{
return TimeSpan.TryParseExact(time, @"hh\:mm\:ss\.fff", _usCulture, out var span)
? span.Ticks
: (TimeSpan.TryParseExact(time, @"hh\:mm\:ss\,fff", _usCulture, out span)
? span.Ticks : 0);
} }
} }
} }

View File

@ -1,477 +1,21 @@
using System; #nullable enable
using System.Collections.Generic;
using System.Globalization; using Microsoft.Extensions.Logging;
using System.IO; using Nikse.SubtitleEdit.Core.SubtitleFormats;
using System.Text;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.MediaEncoding.Subtitles namespace MediaBrowser.MediaEncoding.Subtitles
{ {
/// <summary> /// <summary>
/// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>. /// SubStation Alpha subtitle parser.
/// </summary> /// </summary>
public class SsaParser : ISubtitleParser public class SsaParser : SubtitleEditParser<SubStationAlpha>
{ {
/// <inheritdoc /> /// <summary>
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken) /// Initializes a new instance of the <see cref="SsaParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public SsaParser(ILogger logger) : base(logger)
{ {
var trackInfo = new SubtitleTrackInfo();
var trackEvents = new List<SubtitleTrackEvent>();
using (var reader = new StreamReader(stream))
{
bool eventsStarted = false;
string[] format = "Marked, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text".Split(',');
int indexLayer = 0;
int indexStart = 1;
int indexEnd = 2;
int indexStyle = 3;
int indexName = 4;
int indexEffect = 8;
int indexText = 9;
int lineNumber = 0;
var header = new StringBuilder();
string line;
while ((line = reader.ReadLine()) != null)
{
cancellationToken.ThrowIfCancellationRequested();
lineNumber++;
if (!eventsStarted)
{
header.AppendLine(line);
}
if (string.Equals(line.Trim(), "[events]", StringComparison.OrdinalIgnoreCase))
{
eventsStarted = true;
}
else if (!string.IsNullOrEmpty(line) && line.Trim().StartsWith(';'))
{
// skip comment lines
}
else if (eventsStarted && line.Trim().Length > 0)
{
string s = line.Trim().ToLowerInvariant();
if (s.StartsWith("format:", StringComparison.Ordinal))
{
if (line.Length > 10)
{
format = line.ToLowerInvariant().Substring(8).Split(',');
for (int i = 0; i < format.Length; i++)
{
if (string.Equals(format[i].Trim(), "layer", StringComparison.OrdinalIgnoreCase))
{
indexLayer = i;
}
else if (string.Equals(format[i].Trim(), "start", StringComparison.OrdinalIgnoreCase))
{
indexStart = i;
}
else if (string.Equals(format[i].Trim(), "end", StringComparison.OrdinalIgnoreCase))
{
indexEnd = i;
}
else if (string.Equals(format[i].Trim(), "text", StringComparison.OrdinalIgnoreCase))
{
indexText = i;
}
else if (string.Equals(format[i].Trim(), "effect", StringComparison.OrdinalIgnoreCase))
{
indexEffect = i;
}
else if (string.Equals(format[i].Trim(), "style", StringComparison.OrdinalIgnoreCase))
{
indexStyle = i;
}
}
}
}
else if (!string.IsNullOrEmpty(s))
{
string text = string.Empty;
string start = string.Empty;
string end = string.Empty;
string style = string.Empty;
string layer = string.Empty;
string effect = string.Empty;
string name = string.Empty;
string[] splittedLine;
if (s.StartsWith("dialogue:", StringComparison.Ordinal))
{
splittedLine = line.Substring(10).Split(',');
}
else
{
splittedLine = line.Split(',');
}
for (int i = 0; i < splittedLine.Length; i++)
{
if (i == indexStart)
{
start = splittedLine[i].Trim();
}
else if (i == indexEnd)
{
end = splittedLine[i].Trim();
}
else if (i == indexLayer)
{
layer = splittedLine[i];
}
else if (i == indexEffect)
{
effect = splittedLine[i];
}
else if (i == indexText)
{
text = splittedLine[i];
}
else if (i == indexStyle)
{
style = splittedLine[i];
}
else if (i == indexName)
{
name = splittedLine[i];
}
else if (i > indexText)
{
text += "," + splittedLine[i];
}
}
try
{
trackEvents.Add(
new SubtitleTrackEvent
{
StartPositionTicks = GetTimeCodeFromString(start),
EndPositionTicks = GetTimeCodeFromString(end),
Text = GetFormattedText(text)
});
}
catch
{
}
}
}
}
// if (header.Length > 0)
// subtitle.Header = header.ToString();
// subtitle.Renumber(1);
}
trackInfo.TrackEvents = trackEvents.ToArray();
return trackInfo;
}
private static long GetTimeCodeFromString(string time)
{
// h:mm:ss.cc
string[] timeCode = time.Split(':', '.');
return new TimeSpan(
0,
int.Parse(timeCode[0], CultureInfo.InvariantCulture),
int.Parse(timeCode[1], CultureInfo.InvariantCulture),
int.Parse(timeCode[2], CultureInfo.InvariantCulture),
int.Parse(timeCode[3], CultureInfo.InvariantCulture) * 10).Ticks;
}
private static string GetFormattedText(string text)
{
text = text.Replace("\\n", ParserValues.NewLine, StringComparison.OrdinalIgnoreCase);
for (int i = 0; i < 10; i++) // just look ten times...
{
if (text.Contains(@"{\fn", StringComparison.Ordinal))
{
int start = text.IndexOf(@"{\fn", StringComparison.Ordinal);
int end = text.IndexOf('}', start);
if (end > 0 && !text.Substring(start).StartsWith("{\\fn}", StringComparison.Ordinal))
{
string fontName = text.Substring(start + 4, end - (start + 4));
string extraTags = string.Empty;
CheckAndAddSubTags(ref fontName, ref extraTags, out bool italic);
text = text.Remove(start, end - start + 1);
if (italic)
{
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + "><i>");
}
else
{
text = text.Insert(start, "<font face=\"" + fontName + "\"" + extraTags + ">");
}
int indexOfEndTag = text.IndexOf("{\\fn}", start, StringComparison.Ordinal);
if (indexOfEndTag > 0)
{
text = text.Remove(indexOfEndTag, "{\\fn}".Length).Insert(indexOfEndTag, "</font>");
}
else
{
text += "</font>";
}
}
}
if (text.Contains(@"{\fs", StringComparison.Ordinal))
{
int start = text.IndexOf(@"{\fs", StringComparison.Ordinal);
int end = text.IndexOf('}', start);
if (end > 0 && !text.Substring(start).StartsWith("{\\fs}", StringComparison.Ordinal))
{
string fontSize = text.Substring(start + 4, end - (start + 4));
string extraTags = string.Empty;
CheckAndAddSubTags(ref fontSize, ref extraTags, out bool italic);
if (IsInteger(fontSize))
{
text = text.Remove(start, end - start + 1);
if (italic)
{
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + "><i>");
}
else
{
text = text.Insert(start, "<font size=\"" + fontSize + "\"" + extraTags + ">");
}
int indexOfEndTag = text.IndexOf("{\\fs}", start, StringComparison.Ordinal);
if (indexOfEndTag > 0)
{
text = text.Remove(indexOfEndTag, "{\\fs}".Length).Insert(indexOfEndTag, "</font>");
}
else
{
text += "</font>";
}
}
}
}
if (text.Contains(@"{\c", StringComparison.Ordinal))
{
int start = text.IndexOf(@"{\c", StringComparison.Ordinal);
int end = text.IndexOf('}', start);
if (end > 0 && !text.Substring(start).StartsWith("{\\c}", StringComparison.Ordinal))
{
string color = text.Substring(start + 4, end - (start + 4));
string extraTags = string.Empty;
CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
color = color.PadLeft(6, '0');
// switch to rrggbb from bbggrr
color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
color = color.ToLowerInvariant();
text = text.Remove(start, end - start + 1);
if (italic)
{
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
}
else
{
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
}
int indexOfEndTag = text.IndexOf("{\\c}", start, StringComparison.Ordinal);
if (indexOfEndTag > 0)
{
text = text.Remove(indexOfEndTag, "{\\c}".Length).Insert(indexOfEndTag, "</font>");
}
else
{
text += "</font>";
}
}
}
if (text.Contains(@"{\1c", StringComparison.Ordinal)) // "1" specifices primary color
{
int start = text.IndexOf(@"{\1c", StringComparison.Ordinal);
int end = text.IndexOf('}', start);
if (end > 0 && !text.Substring(start).StartsWith("{\\1c}", StringComparison.Ordinal))
{
string color = text.Substring(start + 5, end - (start + 5));
string extraTags = string.Empty;
CheckAndAddSubTags(ref color, ref extraTags, out bool italic);
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
color = color.PadLeft(6, '0');
// switch to rrggbb from bbggrr
color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
color = color.ToLowerInvariant();
text = text.Remove(start, end - start + 1);
if (italic)
{
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + "><i>");
}
else
{
text = text.Insert(start, "<font color=\"" + color + "\"" + extraTags + ">");
}
int indexOfEndTag = text.IndexOf("{\\1c}", start, StringComparison.Ordinal);
if (indexOfEndTag > 0)
{
text = text.Remove(indexOfEndTag, "{\\1c}".Length).Insert(indexOfEndTag, "</font>");
}
else
{
text += "</font>";
}
}
}
}
text = text.Replace(@"{\i1}", "<i>", StringComparison.Ordinal);
text = text.Replace(@"{\i0}", "</i>", StringComparison.Ordinal);
text = text.Replace(@"{\i}", "</i>", StringComparison.Ordinal);
if (CountTagInText(text, "<i>") > CountTagInText(text, "</i>"))
{
text += "</i>";
}
text = text.Replace(@"{\u1}", "<u>", StringComparison.Ordinal);
text = text.Replace(@"{\u0}", "</u>", StringComparison.Ordinal);
text = text.Replace(@"{\u}", "</u>", StringComparison.Ordinal);
if (CountTagInText(text, "<u>") > CountTagInText(text, "</u>"))
{
text += "</u>";
}
text = text.Replace(@"{\b1}", "<b>", StringComparison.Ordinal);
text = text.Replace(@"{\b0}", "</b>", StringComparison.Ordinal);
text = text.Replace(@"{\b}", "</b>", StringComparison.Ordinal);
if (CountTagInText(text, "<b>") > CountTagInText(text, "</b>"))
{
text += "</b>";
}
return text;
}
private static bool IsInteger(string s)
=> int.TryParse(s, out _);
private static int CountTagInText(string text, string tag)
{
int count = 0;
int index = text.IndexOf(tag, StringComparison.Ordinal);
while (index >= 0)
{
count++;
if (index == text.Length)
{
return count;
}
index = text.IndexOf(tag, index + 1, StringComparison.Ordinal);
}
return count;
}
private static void CheckAndAddSubTags(ref string tagName, ref string extraTags, out bool italic)
{
italic = false;
int indexOfSPlit = tagName.IndexOf('\\', StringComparison.Ordinal);
if (indexOfSPlit > 0)
{
string rest = tagName.Substring(indexOfSPlit).TrimStart('\\');
tagName = tagName.Remove(indexOfSPlit);
for (int i = 0; i < 10; i++)
{
if (rest.StartsWith("fs", StringComparison.Ordinal) && rest.Length > 2)
{
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
string fontSize = rest;
if (indexOfSPlit > 0)
{
fontSize = rest.Substring(0, indexOfSPlit);
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
}
else
{
rest = string.Empty;
}
extraTags += " size=\"" + fontSize.Substring(2) + "\"";
}
else if (rest.StartsWith("fn", StringComparison.Ordinal) && rest.Length > 2)
{
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
string fontName = rest;
if (indexOfSPlit > 0)
{
fontName = rest.Substring(0, indexOfSPlit);
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
}
else
{
rest = string.Empty;
}
extraTags += " face=\"" + fontName.Substring(2) + "\"";
}
else if (rest.StartsWith("c", StringComparison.Ordinal) && rest.Length > 2)
{
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
string fontColor = rest;
if (indexOfSPlit > 0)
{
fontColor = rest.Substring(0, indexOfSPlit);
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
}
else
{
rest = string.Empty;
}
string color = fontColor.Substring(2);
color = color.Replace("&", string.Empty, StringComparison.Ordinal).TrimStart('H');
color = color.PadLeft(6, '0');
// switch to rrggbb from bbggrr
color = "#" + color.Remove(color.Length - 6) + color.Substring(color.Length - 2, 2) + color.Substring(color.Length - 4, 2) + color.Substring(color.Length - 6, 2);
color = color.ToLowerInvariant();
extraTags += " color=\"" + color + "\"";
}
else if (rest.StartsWith("i1", StringComparison.Ordinal) && rest.Length > 1)
{
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
italic = true;
if (indexOfSPlit > 0)
{
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
}
else
{
rest = string.Empty;
}
}
else if (rest.Length > 0 && rest.Contains('\\', StringComparison.Ordinal))
{
indexOfSPlit = rest.IndexOf('\\', StringComparison.Ordinal);
rest = rest.Substring(indexOfSPlit).TrimStart('\\');
}
}
}
} }
} }
} }

View File

@ -0,0 +1,63 @@
#nullable enable
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core;
using ILogger = Microsoft.Extensions.Logging.ILogger;
using SubtitleFormat = Nikse.SubtitleEdit.Core.SubtitleFormats.SubtitleFormat;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// SubStation Alpha subtitle parser.
/// </summary>
/// <typeparam name="T">The <see cref="SubtitleFormat" />.</typeparam>
public abstract class SubtitleEditParser<T> : ISubtitleParser
where T : SubtitleFormat, new()
{
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of the <see cref="SubtitleEditParser{T}"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
protected SubtitleEditParser(ILogger logger)
{
_logger = logger;
}
/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
{
var subtitle = new Subtitle();
var subRip = new T();
var lines = stream.ReadAllLines().ToList();
subRip.LoadSubtitle(subtitle, lines, "untitled");
if (subRip.ErrorCount > 0)
{
_logger.LogError("{ErrorCount} errors encountered while parsing subtitle.");
}
var trackInfo = new SubtitleTrackInfo();
int len = subtitle.Paragraphs.Count;
var trackEvents = new SubtitleTrackEvent[len];
for (int i = 0; i < len; i++)
{
var p = subtitle.Paragraphs[i];
trackEvents[i] = new SubtitleTrackEvent(p.Number.ToString(CultureInfo.InvariantCulture), p.Text)
{
StartPositionTicks = p.StartTime.TimeSpan.Ticks,
EndPositionTicks = p.EndTime.TimeSpan.Ticks
};
}
trackInfo.TrackEvents = trackEvents;
return trackInfo;
}
}
}

View File

@ -27,7 +27,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
{ {
public class SubtitleEncoder : ISubtitleEncoder public class SubtitleEncoder : ISubtitleEncoder
{ {
private readonly ILibraryManager _libraryManager;
private readonly ILogger<SubtitleEncoder> _logger; private readonly ILogger<SubtitleEncoder> _logger;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
@ -42,7 +41,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
new ConcurrentDictionary<string, SemaphoreSlim>(); new ConcurrentDictionary<string, SemaphoreSlim>();
public SubtitleEncoder( public SubtitleEncoder(
ILibraryManager libraryManager,
ILogger<SubtitleEncoder> logger, ILogger<SubtitleEncoder> logger,
IApplicationPaths appPaths, IApplicationPaths appPaths,
IFileSystem fileSystem, IFileSystem fileSystem,
@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
IMediaSourceManager mediaSourceManager) IMediaSourceManager mediaSourceManager)
{ {
_libraryManager = libraryManager;
_logger = logger; _logger = logger;
_appPaths = appPaths; _appPaths = appPaths;
_fileSystem = fileSystem; _fileSystem = fileSystem;
@ -279,12 +276,12 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase)) if (string.Equals(format, SubtitleFormat.SSA, StringComparison.OrdinalIgnoreCase))
{ {
return new SsaParser(); return new SsaParser(_logger);
} }
if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase)) if (string.Equals(format, SubtitleFormat.ASS, StringComparison.OrdinalIgnoreCase))
{ {
return new AssParser(); return new AssParser(_logger);
} }
if (throwIfMissing) if (throwIfMissing)

View File

@ -7,6 +7,13 @@ namespace MediaBrowser.Model.Channels
{ {
public class ChannelFeatures public class ChannelFeatures
{ {
public ChannelFeatures()
{
MediaTypes = Array.Empty<ChannelMediaType>();
ContentTypes = Array.Empty<ChannelMediaContentType>();
DefaultSortFields = Array.Empty<ChannelItemSortField>();
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
@ -38,7 +45,7 @@ namespace MediaBrowser.Model.Channels
public ChannelMediaContentType[] ContentTypes { get; set; } public 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; }
@ -55,7 +62,7 @@ namespace MediaBrowser.Model.Channels
public ChannelItemSortField[] DefaultSortFields { get; set; } public 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.
/// </summary> /// </summary>
public bool SupportsSortOrderToggle { get; set; } public bool SupportsSortOrderToggle { get; set; }
@ -76,12 +83,5 @@ namespace MediaBrowser.Model.Channels
/// </summary> /// </summary>
/// <value><c>true</c> if [supports content downloading]; otherwise, <c>false</c>.</value> /// <value><c>true</c> if [supports content downloading]; otherwise, <c>false</c>.</value>
public bool SupportsContentDownloading { get; set; } public bool SupportsContentDownloading { get; set; }
public ChannelFeatures()
{
MediaTypes = Array.Empty<ChannelMediaType>();
ContentTypes = Array.Empty<ChannelMediaContentType>();
DefaultSortFields = Array.Empty<ChannelItemSortField>();
}
} }
} }

View File

@ -10,7 +10,7 @@ namespace MediaBrowser.Model.Channels
public class ChannelQuery public class ChannelQuery
{ {
/// <summary> /// <summary>
/// Fields to return within the items, in addition to basic information. /// Gets or sets the fields to return within the items, in addition to basic information.
/// </summary> /// </summary>
/// <value>The fields.</value> /// <value>The fields.</value>
public ItemFields[] Fields { get; set; } public ItemFields[] Fields { get; set; }
@ -28,13 +28,13 @@ namespace MediaBrowser.Model.Channels
public Guid UserId { get; set; } public Guid UserId { get; set; }
/// <summary> /// <summary>
/// Skips over a given number of items within the results. Use for paging. /// Gets or sets the start index. Use for paging.
/// </summary> /// </summary>
/// <value>The start index.</value> /// <value>The start index.</value>
public int? StartIndex { get; set; } public int? StartIndex { get; set; }
/// <summary> /// <summary>
/// The maximum number of items to return. /// Gets or sets the maximum number of items to return.
/// </summary> /// </summary>
/// <value>The limit.</value> /// <value>The limit.</value>
public int? Limit { get; set; } public int? Limit { get; set; }

View File

@ -5,6 +5,41 @@ namespace MediaBrowser.Model.Configuration
{ {
public class EncodingOptions public class EncodingOptions
{ {
public EncodingOptions()
{
EnableFallbackFont = false;
DownMixAudioBoost = 2;
MaxMuxingQueueSize = 2048;
EnableThrottling = false;
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
// This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128";
// This is the OpenCL device that is used for tonemapping.
// The left side of the dot is the platform number, and the right side is the device number on the platform.
OpenclDevice = "0.0";
EnableTonemapping = false;
EnableVppTonemapping = false;
TonemappingAlgorithm = "hable";
TonemappingRange = "auto";
TonemappingDesat = 0;
TonemappingThreshold = 0.8;
TonemappingPeak = 100;
TonemappingParam = 0;
H264Crf = 23;
H265Crf = 28;
DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
EnableEnhancedNvdecDecoder = true;
EnableHardwareEncoding = true;
AllowHevcEncoding = true;
EnableSubtitleExtraction = true;
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
}
public int EncodingThreadCount { get; set; } public int EncodingThreadCount { get; set; }
public string TranscodingTempPath { get; set; } public string TranscodingTempPath { get; set; }
@ -24,12 +59,12 @@ namespace MediaBrowser.Model.Configuration
public string HardwareAccelerationType { get; set; } public string HardwareAccelerationType { get; set; }
/// <summary> /// <summary>
/// FFmpeg path as set by the user via the UI. /// Gets or sets the FFmpeg path as set by the user via the UI.
/// </summary> /// </summary>
public string EncoderAppPath { get; set; } public string EncoderAppPath { get; set; }
/// <summary> /// <summary>
/// The current FFmpeg path being used by the system and displayed on the transcode page. /// Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page.
/// </summary> /// </summary>
public string EncoderAppPathDisplay { get; set; } public string EncoderAppPathDisplay { get; set; }
@ -76,40 +111,5 @@ namespace MediaBrowser.Model.Configuration
public bool EnableSubtitleExtraction { get; set; } public bool EnableSubtitleExtraction { get; set; }
public string[] HardwareDecodingCodecs { get; set; } public string[] HardwareDecodingCodecs { get; set; }
public EncodingOptions()
{
EnableFallbackFont = false;
DownMixAudioBoost = 2;
MaxMuxingQueueSize = 2048;
EnableThrottling = false;
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
// This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128";
// This is the OpenCL device that is used for tonemapping.
// The left side of the dot is the platform number, and the right side is the device number on the platform.
OpenclDevice = "0.0";
EnableTonemapping = false;
EnableVppTonemapping = false;
TonemappingAlgorithm = "hable";
TonemappingRange = "auto";
TonemappingDesat = 0;
TonemappingThreshold = 0.8;
TonemappingPeak = 100;
TonemappingParam = 0;
H264Crf = 23;
H265Crf = 28;
DeinterlaceDoubleRate = false;
DeinterlaceMethod = "yadif";
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
EnableEnhancedNvdecDecoder = true;
EnableHardwareEncoding = true;
AllowHevcEncoding = true;
EnableSubtitleExtraction = true;
HardwareDecodingCodecs = new string[] { "h264", "vc1" };
}
} }
} }

View File

@ -6,6 +6,11 @@ namespace MediaBrowser.Model.Configuration
{ {
public class ImageOption public class ImageOption
{ {
public ImageOption()
{
Limit = 1;
}
/// <summary> /// <summary>
/// Gets or sets the type. /// Gets or sets the type.
/// </summary> /// </summary>
@ -23,10 +28,5 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
/// <value>The minimum width.</value> /// <value>The minimum width.</value>
public int MinWidth { get; set; } public int MinWidth { get; set; }
public ImageOption()
{
Limit = 1;
}
} }
} }

View File

@ -2,13 +2,30 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration namespace MediaBrowser.Model.Configuration
{ {
public class LibraryOptions public class LibraryOptions
{ {
public LibraryOptions()
{
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
SkipSubtitlesIfAudioTrackMatches = true;
RequirePerfectSubtitleMatch = true;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
EnableRealtimeMonitor = true;
PathInfos = Array.Empty<MediaPathInfo>();
EnableInternetProviders = true;
EnableAutomaticSeriesGrouping = true;
SeasonZeroDisplayName = "Specials";
}
public bool EnablePhotos { get; set; } public bool EnablePhotos { get; set; }
public bool EnableRealtimeMonitor { get; set; } public bool EnableRealtimeMonitor { get; set; }
@ -79,387 +96,5 @@ namespace MediaBrowser.Model.Configuration
return null; return null;
} }
public LibraryOptions()
{
TypeOptions = Array.Empty<TypeOptions>();
DisabledSubtitleFetchers = Array.Empty<string>();
SubtitleFetcherOrder = Array.Empty<string>();
DisabledLocalMetadataReaders = Array.Empty<string>();
SkipSubtitlesIfAudioTrackMatches = true;
RequirePerfectSubtitleMatch = true;
EnablePhotos = true;
SaveSubtitlesWithMedia = true;
EnableRealtimeMonitor = true;
PathInfos = Array.Empty<MediaPathInfo>();
EnableInternetProviders = true;
EnableAutomaticSeriesGrouping = true;
SeasonZeroDisplayName = "Specials";
}
}
public class MediaPathInfo
{
public string Path { get; set; }
public string NetworkPath { get; set; }
}
public class TypeOptions
{
public string Type { get; set; }
public string[] MetadataFetchers { get; set; }
public string[] MetadataFetcherOrder { get; set; }
public string[] ImageFetchers { get; set; }
public string[] ImageFetcherOrder { get; set; }
public ImageOption[] ImageOptions { get; set; }
public ImageOption GetImageOptions(ImageType type)
{
foreach (var i in ImageOptions)
{
if (i.Type == type)
{
return i;
}
}
if (DefaultImageOptions.TryGetValue(Type, out ImageOption[] options))
{
foreach (var i in options)
{
if (i.Type == type)
{
return i;
}
}
}
return DefaultInstance;
}
public int GetLimit(ImageType type)
{
return GetImageOptions(type).Limit;
}
public int GetMinWidth(ImageType type)
{
return GetImageOptions(type).MinWidth;
}
public bool IsEnabled(ImageType type)
{
return GetLimit(type) > 0;
}
public TypeOptions()
{
MetadataFetchers = Array.Empty<string>();
MetadataFetcherOrder = Array.Empty<string>();
ImageFetchers = Array.Empty<string>();
ImageFetcherOrder = Array.Empty<string>();
ImageOptions = Array.Empty<ImageOption>();
}
public static Dictionary<string, ImageOption[]> DefaultImageOptions = new Dictionary<string, ImageOption[]>
{
{
"Movie", new []
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"MusicVideo", new []
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"Series", new []
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"MusicAlbum", new []
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
}
}
},
{
"MusicArtist", new []
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default
// They do look great, but most artists won't have them, which means a banner view isn't really possible
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
// Don't download this by default
// Generally not used
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"BoxSet", new []
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
}
}
},
{
"Season", new []
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 0,
Type = ImageType.Thumb
}
}
},
{
"Episode", new []
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
}
}
}
};
public static ImageOption DefaultInstance = new ImageOption();
} }
} }

View File

@ -0,0 +1,12 @@
#nullable disable
#pragma warning disable CS1591
namespace MediaBrowser.Model.Configuration
{
public class MediaPathInfo
{
public string Path { get; set; }
public string NetworkPath { get; set; }
}
}

View File

@ -4,11 +4,11 @@ namespace MediaBrowser.Model.Configuration
{ {
public class MetadataConfiguration public class MetadataConfiguration
{ {
public bool UseFileCreationTimeForDateAdded { get; set; }
public MetadataConfiguration() public MetadataConfiguration()
{ {
UseFileCreationTimeForDateAdded = true; UseFileCreationTimeForDateAdded = true;
} }
public bool UseFileCreationTimeForDateAdded { get; set; }
} }
} }

View File

@ -10,6 +10,16 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
public class MetadataOptions public class MetadataOptions
{ {
public MetadataOptions()
{
DisabledMetadataSavers = Array.Empty<string>();
LocalMetadataReaderOrder = Array.Empty<string>();
DisabledMetadataFetchers = Array.Empty<string>();
MetadataFetcherOrder = Array.Empty<string>();
DisabledImageFetchers = Array.Empty<string>();
ImageFetcherOrder = Array.Empty<string>();
}
public string ItemType { get; set; } public string ItemType { get; set; }
public string[] DisabledMetadataSavers { get; set; } public string[] DisabledMetadataSavers { get; set; }
@ -23,15 +33,5 @@ namespace MediaBrowser.Model.Configuration
public string[] DisabledImageFetchers { get; set; } public string[] DisabledImageFetchers { get; set; }
public string[] ImageFetcherOrder { get; set; } public string[] ImageFetcherOrder { get; set; }
public MetadataOptions()
{
DisabledMetadataSavers = Array.Empty<string>();
LocalMetadataReaderOrder = Array.Empty<string>();
DisabledMetadataFetchers = Array.Empty<string>();
MetadataFetcherOrder = Array.Empty<string>();
DisabledImageFetchers = Array.Empty<string>();
ImageFetcherOrder = Array.Empty<string>();
}
} }
} }

View File

@ -8,6 +8,12 @@ namespace MediaBrowser.Model.Configuration
{ {
public class MetadataPluginSummary public class MetadataPluginSummary
{ {
public MetadataPluginSummary()
{
SupportedImageTypes = Array.Empty<ImageType>();
Plugins = Array.Empty<MetadataPlugin>();
}
/// <summary> /// <summary>
/// Gets or sets the type of the item. /// Gets or sets the type of the item.
/// </summary> /// </summary>
@ -25,11 +31,5 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
/// <value>The supported image types.</value> /// <value>The supported image types.</value>
public ImageType[] SupportedImageTypes { get; set; } public ImageType[] SupportedImageTypes { get; set; }
public MetadataPluginSummary()
{
SupportedImageTypes = Array.Empty<ImageType>();
Plugins = Array.Empty<MetadataPlugin>();
}
} }
} }

View File

@ -254,7 +254,7 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets the preferred metadata language. /// Gets or sets the preferred metadata language.
/// </summary> /// </summary>
/// <value>The preferred metadata language.</value> /// <value>The preferred metadata language.</value>
public string PreferredMetadataLanguage { get; set; } = string.Empty; public string PreferredMetadataLanguage { get; set; } = "en";
/// <summary> /// <summary>
/// Gets or sets the metadata country code. /// Gets or sets the metadata country code.
@ -418,8 +418,6 @@ namespace MediaBrowser.Model.Configuration
public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty<PathSubstitution>(); public PathSubstitution[] PathSubstitutions { get; set; } = Array.Empty<PathSubstitution>();
public bool EnableSimpleArtistDetection { get; set; } = false;
public string[] UninstalledPlugins { get; set; } = Array.Empty<string>(); public string[] UninstalledPlugins { get; set; } = Array.Empty<string>();
/// <summary> /// <summary>
@ -461,10 +459,5 @@ namespace MediaBrowser.Model.Configuration
/// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder. /// Gets or sets a value indicating whether older plugins should automatically be deleted from the plugin folder.
/// </summary> /// </summary>
public bool RemoveOldPlugins { get; set; } public bool RemoveOldPlugins { get; set; }
/// <summary>
/// Gets or sets a value indicating whether plugin image should be disabled.
/// </summary>
public bool DisablePluginImages { get; set; }
} }
} }

View File

@ -0,0 +1,365 @@
#nullable disable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration
{
public class TypeOptions
{
public static readonly ImageOption DefaultInstance = new ImageOption();
public static readonly Dictionary<string, ImageOption[]> DefaultImageOptions = new Dictionary<string, ImageOption[]>
{
{
"Movie", new[]
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"MusicVideo", new[]
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"Series", new[]
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"MusicAlbum", new[]
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
}
}
},
{
"MusicArtist", new[]
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
// Don't download this by default
// They do look great, but most artists won't have them, which means a banner view isn't really possible
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
// Don't download this by default
// Generally not used
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
}
}
},
{
"BoxSet", new[]
{
new ImageOption
{
Limit = 1,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 1,
Type = ImageType.Thumb
},
new ImageOption
{
Limit = 1,
Type = ImageType.Logo
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Art
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Disc
},
// Don't download this by default as it's rarely used.
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
}
}
},
{
"Season", new[]
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
},
new ImageOption
{
Limit = 0,
Type = ImageType.Banner
},
new ImageOption
{
Limit = 0,
Type = ImageType.Thumb
}
}
},
{
"Episode", new[]
{
new ImageOption
{
Limit = 0,
MinWidth = 1280,
Type = ImageType.Backdrop
},
new ImageOption
{
Limit = 1,
Type = ImageType.Primary
}
}
}
};
public TypeOptions()
{
MetadataFetchers = Array.Empty<string>();
MetadataFetcherOrder = Array.Empty<string>();
ImageFetchers = Array.Empty<string>();
ImageFetcherOrder = Array.Empty<string>();
ImageOptions = Array.Empty<ImageOption>();
}
public string Type { get; set; }
public string[] MetadataFetchers { get; set; }
public string[] MetadataFetcherOrder { get; set; }
public string[] ImageFetchers { get; set; }
public string[] ImageFetcherOrder { get; set; }
public ImageOption[] ImageOptions { get; set; }
public ImageOption GetImageOptions(ImageType type)
{
foreach (var i in ImageOptions)
{
if (i.Type == type)
{
return i;
}
}
if (DefaultImageOptions.TryGetValue(Type, out ImageOption[] options))
{
foreach (var i in options)
{
if (i.Type == type)
{
return i;
}
}
}
return DefaultInstance;
}
public int GetLimit(ImageType type)
{
return GetImageOptions(type).Limit;
}
public int GetMinWidth(ImageType type)
{
return GetImageOptions(type).MinWidth;
}
public bool IsEnabled(ImageType type)
{
return GetLimit(type) > 0;
}
}
}

View File

@ -11,6 +11,24 @@ namespace MediaBrowser.Model.Configuration
/// </summary> /// </summary>
public class UserConfiguration public class UserConfiguration
{ {
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
public UserConfiguration()
{
EnableNextEpisodeAutoPlay = true;
RememberAudioSelections = true;
RememberSubtitleSelections = true;
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true;
LatestItemsExcludes = Array.Empty<string>();
OrderedViews = Array.Empty<string>();
MyMediaExcludes = Array.Empty<string>();
GroupedFolders = Array.Empty<string>();
}
/// <summary> /// <summary>
/// Gets or sets the audio language preference. /// Gets or sets the audio language preference.
/// </summary> /// </summary>
@ -52,23 +70,5 @@ namespace MediaBrowser.Model.Configuration
public bool RememberSubtitleSelections { get; set; } public bool RememberSubtitleSelections { get; set; }
public bool EnableNextEpisodeAutoPlay { get; set; } public bool EnableNextEpisodeAutoPlay { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
/// </summary>
public UserConfiguration()
{
EnableNextEpisodeAutoPlay = true;
RememberAudioSelections = true;
RememberSubtitleSelections = true;
HidePlayedInLatest = true;
PlayDefaultAudioTrack = true;
LatestItemsExcludes = Array.Empty<string>();
OrderedViews = Array.Empty<string>();
MyMediaExcludes = Array.Empty<string>();
GroupedFolders = Array.Empty<string>();
}
} }
} }

View File

@ -5,6 +5,14 @@ namespace MediaBrowser.Model.Configuration
{ {
public class XbmcMetadataOptions public class XbmcMetadataOptions
{ {
public XbmcMetadataOptions()
{
ReleaseDateFormat = "yyyy-MM-dd";
SaveImagePathsInNfo = true;
EnablePathSubstitution = true;
}
public string UserId { get; set; } public string UserId { get; set; }
public string ReleaseDateFormat { get; set; } public string ReleaseDateFormat { get; set; }
@ -14,13 +22,5 @@ namespace MediaBrowser.Model.Configuration
public bool EnablePathSubstitution { get; set; } public bool EnablePathSubstitution { get; set; }
public bool EnableExtraThumbsDuplication { get; set; } public bool EnableExtraThumbsDuplication { get; set; }
public XbmcMetadataOptions()
{
ReleaseDateFormat = "yyyy-MM-dd";
SaveImagePathsInNfo = true;
EnablePathSubstitution = true;
}
} }
} }

View File

@ -34,20 +34,20 @@ namespace MediaBrowser.Model.Dlna
public DeviceProfile Profile { get; set; } public DeviceProfile Profile { get; set; }
/// <summary> /// <summary>
/// Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested. /// Gets or sets a media source id. Optional. Only needed if a specific AudioStreamIndex or SubtitleStreamIndex are requested.
/// </summary> /// </summary>
public string MediaSourceId { get; set; } public string MediaSourceId { get; set; }
public string DeviceId { get; set; } public string DeviceId { get; set; }
/// <summary> /// <summary>
/// Allows an override of supported number of audio channels /// Gets or sets an override of supported number of audio channels
/// Example: DeviceProfile supports five channel, but user only has stereo speakers /// Example: DeviceProfile supports five channel, but user only has stereo speakers.
/// </summary> /// </summary>
public int? MaxAudioChannels { get; set; } public int? MaxAudioChannels { get; set; }
/// <summary> /// <summary>
/// The application's configured quality setting. /// Gets or sets the application's configured quality setting.
/// </summary> /// </summary>
public int? MaxBitrate { get; set; } public int? MaxBitrate { get; set; }
@ -66,6 +66,7 @@ namespace MediaBrowser.Model.Dlna
/// <summary> /// <summary>
/// Gets the maximum bitrate. /// Gets the maximum bitrate.
/// </summary> /// </summary>
/// <param name="isAudio">Whether or not this is audio.</param>
/// <returns>System.Nullable&lt;System.Int32&gt;.</returns> /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
public int? GetMaxBitrate(bool isAudio) public int? GetMaxBitrate(bool isAudio)
{ {

View File

@ -9,6 +9,12 @@ namespace MediaBrowser.Model.Dlna
{ {
public class CodecProfile public class CodecProfile
{ {
public CodecProfile()
{
Conditions = Array.Empty<ProfileCondition>();
ApplyConditions = Array.Empty<ProfileCondition>();
}
[XmlAttribute("type")] [XmlAttribute("type")]
public CodecType Type { get; set; } public CodecType Type { get; set; }
@ -22,12 +28,6 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } public string Container { get; set; }
public CodecProfile()
{
Conditions = Array.Empty<ProfileCondition>();
ApplyConditions = Array.Empty<ProfileCondition>();
}
public string[] GetCodecs() public string[] GetCodecs()
{ {
return ContainerProfile.SplitValue(Codec); return ContainerProfile.SplitValue(Codec);

View File

@ -1,8 +1,8 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Linq;
using System.Globalization; using System.Globalization;
using System.Linq;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
namespace MediaBrowser.Model.Dlna namespace MediaBrowser.Model.Dlna

View File

@ -9,6 +9,11 @@ namespace MediaBrowser.Model.Dlna
{ {
public class ContainerProfile public class ContainerProfile
{ {
public ContainerProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
[XmlAttribute("type")] [XmlAttribute("type")]
public DlnaProfileType Type { get; set; } public DlnaProfileType Type { get; set; }
@ -17,11 +22,6 @@ namespace MediaBrowser.Model.Dlna
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } public string Container { get; set; }
public ContainerProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
public string[] GetContainers() public string[] GetContainers()
{ {
return SplitValue(Container); return SplitValue(Container);

View File

@ -81,13 +81,13 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.DlnaV15; DlnaFlags.DlnaV15;
// if (isDirectStream) // if (isDirectStream)
//{ // {
// flagValue = flagValue | DlnaFlags.ByteBasedSeek; // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
//} // }
// else if (runtimeTicks.HasValue) // else if (runtimeTicks.HasValue)
//{ // {
// flagValue = flagValue | DlnaFlags.TimeBasedSeek; // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
//} // }
string dlnaflags = string.Format( string dlnaflags = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
@ -150,16 +150,18 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.DlnaV15; DlnaFlags.DlnaV15;
// if (isDirectStream) // if (isDirectStream)
//{ // {
// flagValue = flagValue | DlnaFlags.ByteBasedSeek; // flagValue = flagValue | DlnaFlags.ByteBasedSeek;
//} // }
// else if (runtimeTicks.HasValue) // else if (runtimeTicks.HasValue)
//{ // {
// flagValue = flagValue | DlnaFlags.TimeBasedSeek; // flagValue = flagValue | DlnaFlags.TimeBasedSeek;
//} // }
string dlnaflags = string.Format(CultureInfo.InvariantCulture, ";DLNA.ORG_FLAGS={0}", string dlnaflags = string.Format(
DlnaMaps.FlagsToString(flagValue)); CultureInfo.InvariantCulture,
";DLNA.ORG_FLAGS={0}",
DlnaMaps.FlagsToString(flagValue));
ResponseProfile mediaProfile = _profile.GetVideoMediaProfile( ResponseProfile mediaProfile = _profile.GetVideoMediaProfile(
container, container,

View File

@ -8,6 +8,7 @@ namespace MediaBrowser.Model.Dlna
public interface IDeviceDiscovery public interface IDeviceDiscovery
{ {
event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered; event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft; event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
} }
} }

View File

@ -57,7 +57,6 @@ namespace MediaBrowser.Model.Dlna
string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) || string.Equals(container, "mpegts", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase)) string.Equals(container, "m2ts", StringComparison.OrdinalIgnoreCase))
{ {
return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType); return ResolveVideoMPEG2TSFormat(videoCodec, audioCodec, width, height, timestampType);
} }
@ -323,7 +322,6 @@ namespace MediaBrowser.Model.Dlna
if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) && if (string.Equals(videoCodec, "wmv", StringComparison.OrdinalIgnoreCase) &&
(string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase))) (string.IsNullOrEmpty(audioCodec) || string.Equals(audioCodec, "wma", StringComparison.OrdinalIgnoreCase) || string.Equals(videoCodec, "wmapro", StringComparison.OrdinalIgnoreCase)))
{ {
if (width.HasValue && height.HasValue) if (width.HasValue && height.HasValue)
{ {
if ((width.Value <= 720) && (height.Value <= 576)) if ((width.Value <= 720) && (height.Value <= 576))
@ -479,7 +477,9 @@ namespace MediaBrowser.Model.Dlna
{ {
if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) || if (string.Equals(container, "jpeg", StringComparison.OrdinalIgnoreCase) ||
string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase)) string.Equals(container, "jpg", StringComparison.OrdinalIgnoreCase))
{
return ResolveImageJPGFormat(width, height); return ResolveImageJPGFormat(width, height);
}
if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase)) if (string.Equals(container, "png", StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -4,14 +4,14 @@ namespace MediaBrowser.Model.Dlna
{ {
public class ResolutionConfiguration public class ResolutionConfiguration
{ {
public int MaxWidth { get; set; }
public int MaxBitrate { get; set; }
public ResolutionConfiguration(int maxWidth, int maxBitrate) public ResolutionConfiguration(int maxWidth, int maxBitrate)
{ {
MaxWidth = maxWidth; MaxWidth = maxWidth;
MaxBitrate = maxBitrate; MaxBitrate = maxBitrate;
} }
public int MaxWidth { get; set; }
public int MaxBitrate { get; set; }
} }
} }

View File

@ -8,6 +8,11 @@ namespace MediaBrowser.Model.Dlna
{ {
public class ResponseProfile public class ResponseProfile
{ {
public ResponseProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
[XmlAttribute("container")] [XmlAttribute("container")]
public string Container { get; set; } public string Container { get; set; }
@ -28,11 +33,6 @@ namespace MediaBrowser.Model.Dlna
public ProfileCondition[] Conditions { get; set; } public ProfileCondition[] Conditions { get; set; }
public ResponseProfile()
{
Conditions = Array.Empty<ProfileCondition>();
}
public string[] GetContainers() public string[] GetContainers()
{ {
return ContainerProfile.SplitValue(Container); return ContainerProfile.SplitValue(Container);

View File

@ -7,6 +7,46 @@ namespace MediaBrowser.Model.Dlna
{ {
public class SearchCriteria public class SearchCriteria
{ {
public SearchCriteria(string search)
{
if (search.Length == 0)
{
throw new ArgumentException("String can't be empty.", nameof(search));
}
SearchType = SearchType.Unknown;
string[] factors = RegexSplit(search, "(and|or)");
foreach (string factor in factors)
{
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3);
if (subFactors.Length == 3)
{
if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase)
&& (string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase)))
{
if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Image;
}
else if (string.Equals("\"object.item.videoItem\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Video;
}
else if (string.Equals("\"object.container.playlistContainer\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Playlist;
}
else if (string.Equals("\"object.container.album.musicAlbum\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.MusicAlbum;
}
}
}
}
}
public SearchType SearchType { get; set; } public SearchType SearchType { get; set; }
/// <summary> /// <summary>
@ -31,45 +71,5 @@ namespace MediaBrowser.Model.Dlna
{ {
return Regex.Split(str, term, RegexOptions.IgnoreCase); return Regex.Split(str, term, RegexOptions.IgnoreCase);
} }
public SearchCriteria(string search)
{
if (search.Length == 0)
{
throw new ArgumentException("String can't be empty.", nameof(search));
}
SearchType = SearchType.Unknown;
string[] factors = RegexSplit(search, "(and|or)");
foreach (string factor in factors)
{
string[] subFactors = RegexSplit(factor.Trim().Trim('(').Trim(')').Trim(), "\\s", 3);
if (subFactors.Length == 3)
{
if (string.Equals("upnp:class", subFactors[0], StringComparison.OrdinalIgnoreCase) &&
(string.Equals("=", subFactors[1], StringComparison.Ordinal) || string.Equals("derivedfrom", subFactors[1], StringComparison.OrdinalIgnoreCase)))
{
if (string.Equals("\"object.item.imageItem\"", subFactors[2], StringComparison.Ordinal) || string.Equals("\"object.item.imageItem.photo\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Image;
}
else if (string.Equals("\"object.item.videoItem\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Video;
}
else if (string.Equals("\"object.container.playlistContainer\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.Playlist;
}
else if (string.Equals("\"object.container.album.musicAlbum\"", subFactors[2], StringComparison.OrdinalIgnoreCase))
{
SearchType = SearchType.MusicAlbum;
}
}
}
}
}
} }
} }

View File

@ -6,10 +6,10 @@ namespace MediaBrowser.Model.Dlna
{ {
public class SortCriteria public class SortCriteria
{ {
public SortOrder SortOrder => SortOrder.Ascending;
public SortCriteria(string value) public SortCriteria(string value)
{ {
} }
public SortOrder SortOrder => SortOrder.Ascending;
} }
} }

View File

@ -227,7 +227,7 @@ namespace MediaBrowser.Model.Dlna
} }
} }
public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, string _, DeviceProfile profile, DlnaProfileType type) public static string NormalizeMediaSourceFormatIntoSingleContainer(string inputContainer, DeviceProfile profile, DlnaProfileType type)
{ {
if (string.IsNullOrEmpty(inputContainer)) if (string.IsNullOrEmpty(inputContainer))
{ {
@ -274,14 +274,14 @@ namespace MediaBrowser.Model.Dlna
if (options.ForceDirectPlay) if (options.ForceDirectPlay)
{ {
playlistItem.PlayMethod = PlayMethod.DirectPlay; playlistItem.PlayMethod = PlayMethod.DirectPlay;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem; return playlistItem;
} }
if (options.ForceDirectStream) if (options.ForceDirectStream)
{ {
playlistItem.PlayMethod = PlayMethod.DirectStream; playlistItem.PlayMethod = PlayMethod.DirectStream;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem; return playlistItem;
} }
@ -349,7 +349,7 @@ namespace MediaBrowser.Model.Dlna
playlistItem.PlayMethod = PlayMethod.DirectStream; playlistItem.PlayMethod = PlayMethod.DirectStream;
} }
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Audio); playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Audio);
return playlistItem; return playlistItem;
} }
@ -698,7 +698,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlay != null) if (directPlay != null)
{ {
playlistItem.PlayMethod = directPlay.Value; playlistItem.PlayMethod = directPlay.Value;
playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, item.Path, options.Profile, DlnaProfileType.Video); playlistItem.Container = NormalizeMediaSourceFormatIntoSingleContainer(item.Container, options.Profile, DlnaProfileType.Video);
if (subtitleStream != null) if (subtitleStream != null)
{ {
@ -1404,7 +1404,9 @@ namespace MediaBrowser.Model.Dlna
{ {
_logger.LogInformation( _logger.LogInformation(
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}", "Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
playMethod, itemBitrate, requestedMaxBitrate); playMethod,
itemBitrate,
requestedMaxBitrate);
return false; return false;
} }
@ -1733,7 +1735,7 @@ namespace MediaBrowser.Model.Dlna
if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny) if (condition.Condition == ProfileConditionType.Equals || condition.Condition == ProfileConditionType.EqualsAny)
{ {
item.SetOption(qualifier, "profile", string.Join(",", values)); item.SetOption(qualifier, "profile", string.Join(',', values));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto
public NameGuidPair[] GenreItems { get; set; } public NameGuidPair[] GenreItems { get; set; }
/// <summary> /// <summary>
/// If the item does not have a logo, this will hold the Id of the Parent that has one. /// Gets or sets wether the item has a logo, this will hold the Id of the Parent that has one.
/// </summary> /// </summary>
/// <value>The parent logo item id.</value> /// <value>The parent logo item id.</value>
public string ParentLogoItemId { get; set; } public string ParentLogoItemId { get; set; }
/// <summary> /// <summary>
/// If the item does not have any backdrops, this will hold the Id of the Parent that has one. /// Gets or sets wether the item has any backdrops, this will hold the Id of the Parent that has one.
/// </summary> /// </summary>
/// <value>The parent backdrop item id.</value> /// <value>The parent backdrop item id.</value>
public string ParentBackdropItemId { get; set; } public string ParentBackdropItemId { get; set; }
@ -318,7 +318,7 @@ namespace MediaBrowser.Model.Dto
public int? LocalTrailerCount { get; set; } public int? LocalTrailerCount { get; set; }
/// <summary> /// <summary>
/// User data for this item based on the user it's being requested for. /// Gets or sets the user data for this item based on the user it's being requested for.
/// </summary> /// </summary>
/// <value>The user data.</value> /// <value>The user data.</value>
public UserItemDataDto UserData { get; set; } public UserItemDataDto UserData { get; set; }
@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto
public string ParentLogoImageTag { get; set; } public string ParentLogoImageTag { get; set; }
/// <summary> /// <summary>
/// If the item does not have a art, this will hold the Id of the Parent that has one. /// Gets or sets wether the item has fan art, this will hold the Id of the Parent that has one.
/// </summary> /// </summary>
/// <value>The parent art item id.</value> /// <value>The parent art item id.</value>
public string ParentArtItemId { get; set; } public string ParentArtItemId { get; set; }
@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dto
public string ChannelPrimaryImageTag { get; set; } public string ChannelPrimaryImageTag { 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; }

View File

@ -12,6 +12,18 @@ namespace MediaBrowser.Model.Dto
{ {
public class MediaSourceInfo public class MediaSourceInfo
{ {
public MediaSourceInfo()
{
Formats = Array.Empty<string>();
MediaStreams = new List<MediaStream>();
MediaAttachments = Array.Empty<MediaAttachment>();
RequiredHttpHeaders = new Dictionary<string, string>();
SupportsTranscoding = true;
SupportsDirectStream = true;
SupportsDirectPlay = true;
SupportsProbing = true;
}
public MediaProtocol Protocol { get; set; } public MediaProtocol Protocol { get; set; }
public string Id { get; set; } public string Id { get; set; }
@ -31,6 +43,7 @@ namespace MediaBrowser.Model.Dto
public string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the media is remote.
/// Differentiate internet url vs local network. /// Differentiate internet url vs local network.
/// </summary> /// </summary>
public bool IsRemote { get; set; } public bool IsRemote { get; set; }
@ -95,16 +108,28 @@ namespace MediaBrowser.Model.Dto
public int? AnalyzeDurationMs { get; set; } public int? AnalyzeDurationMs { get; set; }
public MediaSourceInfo() [JsonIgnore]
public TranscodeReason[] TranscodeReasons { get; set; }
public int? DefaultAudioStreamIndex { get; set; }
public int? DefaultSubtitleStreamIndex { get; set; }
[JsonIgnore]
public MediaStream VideoStream
{ {
Formats = Array.Empty<string>(); get
MediaStreams = new List<MediaStream>(); {
MediaAttachments = Array.Empty<MediaAttachment>(); foreach (var i in MediaStreams)
RequiredHttpHeaders = new Dictionary<string, string>(); {
SupportsTranscoding = true; if (i.Type == MediaStreamType.Video)
SupportsDirectStream = true; {
SupportsDirectPlay = true; return i;
SupportsProbing = true; }
}
return null;
}
} }
public void InferTotalBitrate(bool force = false) public void InferTotalBitrate(bool force = false)
@ -134,13 +159,6 @@ namespace MediaBrowser.Model.Dto
} }
} }
[JsonIgnore]
public TranscodeReason[] TranscodeReasons { get; set; }
public int? DefaultAudioStreamIndex { get; set; }
public int? DefaultSubtitleStreamIndex { get; set; }
public MediaStream GetDefaultAudioStream(int? defaultIndex) public MediaStream GetDefaultAudioStream(int? defaultIndex)
{ {
if (defaultIndex.HasValue) if (defaultIndex.HasValue)
@ -175,23 +193,6 @@ namespace MediaBrowser.Model.Dto
return null; return null;
} }
[JsonIgnore]
public MediaStream VideoStream
{
get
{
foreach (var i in MediaStreams)
{
if (i.Type == MediaStreamType.Video)
{
return i;
}
}
return null;
}
}
public MediaStream GetMediaStream(MediaStreamType type, int index) public MediaStream GetMediaStream(MediaStreamType type, int index)
{ {
foreach (var i in MediaStreams) foreach (var i in MediaStreams)

View File

@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
{ {
public class MetadataEditorInfo public class MetadataEditorInfo
{ {
public MetadataEditorInfo()
{
ParentalRatingOptions = Array.Empty<ParentalRating>();
Countries = Array.Empty<CountryInfo>();
Cultures = Array.Empty<CultureDto>();
ExternalIdInfos = Array.Empty<ExternalIdInfo>();
ContentTypeOptions = Array.Empty<NameValuePair>();
}
public ParentalRating[] ParentalRatingOptions { get; set; } public ParentalRating[] ParentalRatingOptions { get; set; }
public CountryInfo[] Countries { get; set; } public CountryInfo[] Countries { get; set; }
@ -21,14 +30,5 @@ namespace MediaBrowser.Model.Dto
public string ContentType { get; set; } public string ContentType { get; set; }
public NameValuePair[] ContentTypeOptions { get; set; } public NameValuePair[] ContentTypeOptions { get; set; }
public MetadataEditorInfo()
{
ParentalRatingOptions = Array.Empty<ParentalRating>();
Countries = Array.Empty<CountryInfo>();
Cultures = Array.Empty<CultureDto>();
ExternalIdInfos = Array.Empty<ExternalIdInfo>();
ContentTypeOptions = Array.Empty<NameValuePair>();
}
} }
} }

View File

@ -0,0 +1,14 @@
#nullable disable
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Model.Dto
{
public class NameGuidPair
{
public string Name { get; set; }
public Guid Id { get; set; }
}
}

View File

@ -19,11 +19,4 @@ namespace MediaBrowser.Model.Dto
/// <value>The identifier.</value> /// <value>The identifier.</value>
public string Id { get; set; } public string Id { get; set; }
} }
public class NameGuidPair
{
public string Name { get; set; }
public Guid Id { get; set; }
}
} }

View File

@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
/// </summary> /// </summary>
public class UserDto : IItemDto, IHasServerId public class UserDto : IItemDto, IHasServerId
{ {
/// <summary>
/// Initializes a new instance of the <see cref="UserDto"/> class.
/// </summary>
public UserDto()
{
Configuration = new UserConfiguration();
Policy = new UserPolicy();
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
@ -94,15 +103,6 @@ namespace MediaBrowser.Model.Dto
/// <value>The primary image aspect ratio.</value> /// <value>The primary image aspect ratio.</value>
public double? PrimaryImageAspectRatio { get; set; } public double? PrimaryImageAspectRatio { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserDto"/> class.
/// </summary>
public UserDto()
{
Configuration = new UserConfiguration();
Policy = new UserPolicy();
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {

View File

@ -24,36 +24,4 @@ namespace MediaBrowser.Model.Entities
public const string Playlists = "playlists"; public const string Playlists = "playlists";
public const string Folders = "folders"; public const string Folders = "folders";
} }
public static class SpecialFolder
{
public const string TvShowSeries = "TvShowSeries";
public const string TvGenres = "TvGenres";
public const string TvGenre = "TvGenre";
public const string TvLatest = "TvLatest";
public const string TvNextUp = "TvNextUp";
public const string TvResume = "TvResume";
public const string TvFavoriteSeries = "TvFavoriteSeries";
public const string TvFavoriteEpisodes = "TvFavoriteEpisodes";
public const string MovieLatest = "MovieLatest";
public const string MovieResume = "MovieResume";
public const string MovieMovies = "MovieMovies";
public const string MovieCollections = "MovieCollections";
public const string MovieFavorites = "MovieFavorites";
public const string MovieGenres = "MovieGenres";
public const string MovieGenre = "MovieGenre";
public const string MusicArtists = "MusicArtists";
public const string MusicAlbumArtists = "MusicAlbumArtists";
public const string MusicAlbums = "MusicAlbums";
public const string MusicGenres = "MusicGenres";
public const string MusicLatest = "MusicLatest";
public const string MusicPlaylists = "MusicPlaylists";
public const string MusicSongs = "MusicSongs";
public const string MusicFavorites = "MusicFavorites";
public const string MusicFavoriteArtists = "MusicFavoriteArtists";
public const string MusicFavoriteAlbums = "MusicFavoriteAlbums";
public const string MusicFavoriteSongs = "MusicFavoriteSongs";
}
} }

View File

@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Entities
public string Title { get; set; } public string Title { get; set; }
/// <summary> /// <summary>
/// Gets or sets the video range. /// Gets the video range.
/// </summary> /// </summary>
/// <value>The video range.</value> /// <value>The video range.</value>
public string VideoRange public string VideoRange
@ -108,11 +108,11 @@ namespace MediaBrowser.Model.Entities
} }
} }
public string localizedUndefined { get; set; } public string LocalizedUndefined { get; set; }
public string localizedDefault { get; set; } public string LocalizedDefault { get; set; }
public string localizedForced { get; set; } public string LocalizedForced { get; set; }
public string DisplayTitle public string DisplayTitle
{ {
@ -154,7 +154,7 @@ namespace MediaBrowser.Model.Entities
if (IsDefault) if (IsDefault)
{ {
attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
} }
if (!string.IsNullOrEmpty(Title)) if (!string.IsNullOrEmpty(Title))
@ -211,7 +211,7 @@ namespace MediaBrowser.Model.Entities
return result.ToString(); return result.ToString();
} }
return string.Join(" ", attributes); return string.Join(' ', attributes);
} }
case MediaStreamType.Subtitle: case MediaStreamType.Subtitle:
@ -229,17 +229,17 @@ namespace MediaBrowser.Model.Entities
} }
else else
{ {
attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined); attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined);
} }
if (IsDefault) if (IsDefault)
{ {
attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault); attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
} }
if (IsForced) if (IsForced)
{ {
attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced); attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
} }
if (!string.IsNullOrEmpty(Title)) if (!string.IsNullOrEmpty(Title))
@ -266,67 +266,6 @@ namespace MediaBrowser.Model.Entities
} }
} }
private string GetResolutionText()
{
var i = this;
if (i.Width.HasValue && i.Height.HasValue)
{
var width = i.Width.Value;
var height = i.Height.Value;
if (width >= 3800 || height >= 2000)
{
return "4K";
}
if (width >= 2500)
{
if (i.IsInterlaced)
{
return "1440i";
}
return "1440p";
}
if (width >= 1900 || height >= 1000)
{
if (i.IsInterlaced)
{
return "1080i";
}
return "1080p";
}
if (width >= 1260 || height >= 700)
{
if (i.IsInterlaced)
{
return "720i";
}
return "720p";
}
if (width >= 700 || height >= 440)
{
if (i.IsInterlaced)
{
return "480i";
}
return "480p";
}
return "SD";
}
return null;
}
public string NalLengthSize { get; set; } public string NalLengthSize { get; set; }
/// <summary> /// <summary>
@ -487,6 +426,96 @@ namespace MediaBrowser.Model.Entities
} }
} }
/// <summary>
/// Gets or sets a value indicating whether [supports external stream].
/// </summary>
/// <value><c>true</c> if [supports external stream]; otherwise, <c>false</c>.</value>
public bool SupportsExternalStream { get; set; }
/// <summary>
/// Gets or sets the filename.
/// </summary>
/// <value>The filename.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the pixel format.
/// </summary>
/// <value>The pixel format.</value>
public string PixelFormat { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
/// <value>The level.</value>
public double? Level { get; set; }
/// <summary>
/// Gets or sets whether this instance is anamorphic.
/// </summary>
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
public bool? IsAnamorphic { get; set; }
private string GetResolutionText()
{
var i = this;
if (i.Width.HasValue && i.Height.HasValue)
{
var width = i.Width.Value;
var height = i.Height.Value;
if (width >= 3800 || height >= 2000)
{
return "4K";
}
if (width >= 2500)
{
if (i.IsInterlaced)
{
return "1440i";
}
return "1440p";
}
if (width >= 1900 || height >= 1000)
{
if (i.IsInterlaced)
{
return "1080i";
}
return "1080p";
}
if (width >= 1260 || height >= 700)
{
if (i.IsInterlaced)
{
return "720i";
}
return "720p";
}
if (width >= 700 || height >= 440)
{
if (i.IsInterlaced)
{
return "480i";
}
return "480p";
}
return "SD";
}
return null;
}
public static bool IsTextFormat(string format) public static bool IsTextFormat(string format)
{ {
string codec = format ?? string.Empty; string codec = format ?? string.Empty;
@ -533,35 +562,5 @@ namespace MediaBrowser.Model.Entities
return true; return true;
} }
/// <summary>
/// Gets or sets a value indicating whether [supports external stream].
/// </summary>
/// <value><c>true</c> if [supports external stream]; otherwise, <c>false</c>.</value>
public bool SupportsExternalStream { get; set; }
/// <summary>
/// Gets or sets the filename.
/// </summary>
/// <value>The filename.</value>
public string Path { get; set; }
/// <summary>
/// Gets or sets the pixel format.
/// </summary>
/// <value>The pixel format.</value>
public string PixelFormat { get; set; }
/// <summary>
/// Gets or sets the level.
/// </summary>
/// <value>The level.</value>
public double? Level { get; set; }
/// <summary>
/// Gets a value indicating whether this instance is anamorphic.
/// </summary>
/// <value><c>true</c> if this instance is anamorphic; otherwise, <c>false</c>.</value>
public bool? IsAnamorphic { get; set; }
} }
} }

View File

@ -1,40 +0,0 @@
#nullable disable
#pragma warning disable CS1591
using System;
namespace MediaBrowser.Model.Entities
{
public class PackageReviewInfo
{
/// <summary>
/// Gets or sets the package id (database key) for this review.
/// </summary>
public int id { get; set; }
/// <summary>
/// Gets or sets the rating value.
/// </summary>
public int rating { get; set; }
/// <summary>
/// Gets or sets whether or not this review recommends this item.
/// </summary>
public bool recommend { get; set; }
/// <summary>
/// Gets or sets a short description of the review.
/// </summary>
public string title { get; set; }
/// <summary>
/// Gets or sets the full review.
/// </summary>
public string review { get; set; }
/// <summary>
/// Gets or sets the time of review.
/// </summary>
public DateTime timestamp { get; set; }
}
}

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace MediaBrowser.Model.Entities namespace MediaBrowser.Model.Entities
{ {
@ -9,14 +10,50 @@ namespace MediaBrowser.Model.Entities
public static class ProviderIdsExtensions public static class ProviderIdsExtensions
{ {
/// <summary> /// <summary>
/// Determines whether [has provider identifier] [the specified instance]. /// Gets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
/// <param name="id">The provider id.</param>
/// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
public static bool TryGetProviderId(this IHasProviderIds instance, string name, [MaybeNullWhen(false)] out string id)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (instance.ProviderIds == null)
{
id = null;
return false;
}
return instance.ProviderIds.TryGetValue(name, out id);
}
/// <summary>
/// Gets a provider id.
/// </summary> /// </summary>
/// <param name="instance">The instance.</param> /// <param name="instance">The instance.</param>
/// <param name="provider">The provider.</param> /// <param name="provider">The provider.</param>
/// <returns><c>true</c> if [has provider identifier] [the specified instance]; otherwise, <c>false</c>.</returns> /// <param name="id">The provider id.</param>
public static bool HasProviderId(this IHasProviderIds instance, MetadataProvider provider) /// <returns><c>true</c> if a provider id with the given name was found; otherwise <c>false</c>.</returns>
public static bool TryGetProviderId(this IHasProviderIds instance, MetadataProvider provider, [MaybeNullWhen(false)] out string id)
{ {
return !string.IsNullOrEmpty(instance.GetProviderId(provider.ToString())); return instance.TryGetProviderId(provider.ToString(), out id);
}
/// <summary>
/// Gets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
/// <returns>System.String.</returns>
public static string? GetProviderId(this IHasProviderIds instance, string name)
{
instance.TryGetProviderId(name, out string? id);
return id;
} }
/// <summary> /// <summary>
@ -30,28 +67,6 @@ namespace MediaBrowser.Model.Entities
return instance.GetProviderId(provider.ToString()); return instance.GetProviderId(provider.ToString());
} }
/// <summary>
/// Gets a provider id.
/// </summary>
/// <param name="instance">The instance.</param>
/// <param name="name">The name.</param>
/// <returns>System.String.</returns>
public static string? GetProviderId(this IHasProviderIds instance, string name)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (instance.ProviderIds == null)
{
return null;
}
instance.ProviderIds.TryGetValue(name, out string? id);
return string.IsNullOrEmpty(id) ? null : id;
}
/// <summary> /// <summary>
/// Sets a provider id. /// Sets a provider id.
/// </summary> /// </summary>
@ -68,13 +83,7 @@ namespace MediaBrowser.Model.Entities
// If it's null remove the key from the dictionary // If it's null remove the key from the dictionary
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{ {
if (instance.ProviderIds != null) instance.ProviderIds?.Remove(name);
{
if (instance.ProviderIds.ContainsKey(name))
{
instance.ProviderIds.Remove(name);
}
}
} }
else else
{ {

View File

@ -0,0 +1,36 @@
#pragma warning disable CS1591
namespace MediaBrowser.Model.Entities
{
public static class SpecialFolder
{
public const string TvShowSeries = "TvShowSeries";
public const string TvGenres = "TvGenres";
public const string TvGenre = "TvGenre";
public const string TvLatest = "TvLatest";
public const string TvNextUp = "TvNextUp";
public const string TvResume = "TvResume";
public const string TvFavoriteSeries = "TvFavoriteSeries";
public const string TvFavoriteEpisodes = "TvFavoriteEpisodes";
public const string MovieLatest = "MovieLatest";
public const string MovieResume = "MovieResume";
public const string MovieMovies = "MovieMovies";
public const string MovieCollections = "MovieCollections";
public const string MovieFavorites = "MovieFavorites";
public const string MovieGenres = "MovieGenres";
public const string MovieGenre = "MovieGenre";
public const string MusicArtists = "MusicArtists";
public const string MusicAlbumArtists = "MusicAlbumArtists";
public const string MusicAlbums = "MusicAlbums";
public const string MusicGenres = "MusicGenres";
public const string MusicLatest = "MusicLatest";
public const string MusicPlaylists = "MusicPlaylists";
public const string MusicSongs = "MusicSongs";
public const string MusicFavorites = "MusicFavorites";
public const string MusicFavoriteArtists = "MusicFavoriteArtists";
public const string MusicFavoriteAlbums = "MusicFavoriteAlbums";
public const string MusicFavoriteSongs = "MusicFavoriteSongs";
}
}

View File

@ -11,6 +11,14 @@ namespace MediaBrowser.Model.Entities
/// </summary> /// </summary>
public class VirtualFolderInfo public class VirtualFolderInfo
{ {
/// <summary>
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
/// </summary>
public VirtualFolderInfo()
{
Locations = Array.Empty<string>();
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
@ -31,14 +39,6 @@ namespace MediaBrowser.Model.Entities
public LibraryOptions LibraryOptions { get; set; } public LibraryOptions LibraryOptions { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="VirtualFolderInfo"/> class.
/// </summary>
public VirtualFolderInfo()
{
Locations = Array.Empty<string>();
}
/// <summary> /// <summary>
/// Gets or sets the item identifier. /// Gets or sets the item identifier.
/// </summary> /// </summary>

View File

@ -10,6 +10,11 @@ namespace MediaBrowser.Model.Globalization
/// </summary> /// </summary>
public class CultureDto public class CultureDto
{ {
public CultureDto()
{
ThreeLetterISOLanguageNames = Array.Empty<string>();
}
/// <summary> /// <summary>
/// Gets or sets the name. /// Gets or sets the name.
/// </summary> /// </summary>
@ -29,7 +34,7 @@ namespace MediaBrowser.Model.Globalization
public string TwoLetterISOLanguageName { get; set; } public string TwoLetterISOLanguageName { get; set; }
/// <summary> /// <summary>
/// Gets or sets the name of the three letter ISO language. /// Gets the name of the three letter ISO language.
/// </summary> /// </summary>
/// <value>The name of the three letter ISO language.</value> /// <value>The name of the three letter ISO language.</value>
public string ThreeLetterISOLanguageName public string ThreeLetterISOLanguageName
@ -47,10 +52,5 @@ namespace MediaBrowser.Model.Globalization
} }
public string[] ThreeLetterISOLanguageNames { get; set; } public string[] ThreeLetterISOLanguageNames { get; set; }
public CultureDto()
{
ThreeLetterISOLanguageNames = Array.Empty<string>();
}
} }
} }

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