merge branch 'master' into auto-manifest

pull/5217/head
dkanada 4 years ago
commit bc746b4d05

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

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

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

@ -235,7 +235,13 @@ namespace Emby.Dlna.PlayTo
_logger.LogDebug("Setting mute");
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);
IsMuted = mute;
@ -270,7 +276,13 @@ namespace Emby.Dlna.PlayTo
// Remote control will perform better
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);
}
@ -291,7 +303,13 @@ namespace Emby.Dlna.PlayTo
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);
RestartTimer(true);
@ -325,14 +343,21 @@ namespace Emby.Dlna.PlayTo
}
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);
await Task.Delay(50).ConfigureAwait(false);
await Task.Delay(50, cancellationToken).ConfigureAwait(false);
try
{
await SetPlay(avCommands, CancellationToken.None).ConfigureAwait(false);
await SetPlay(avCommands, cancellationToken).ConfigureAwait(false);
}
catch
{
@ -396,7 +421,13 @@ namespace Emby.Dlna.PlayTo
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);
RestartTimer(true);
@ -414,7 +445,13 @@ namespace Emby.Dlna.PlayTo
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);
TransportState = TransportState.Paused;
@ -990,7 +1027,7 @@ namespace Emby.Dlna.PlayTo
var deviceProperties = new DeviceInfo()
{
Name = string.Join(" ", friendlyNames),
Name = string.Join(' ', friendlyNames),
BaseUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", url.Host, url.Port)
};

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

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

@ -73,7 +73,7 @@ namespace Emby.Naming.AudioBook
var haveChaptersOrPages = stackFiles.Any(x => x.ChapterNumber != null || x.PartNumber != null);
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)
{

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

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

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using SQLitePCL.pretty;
@ -59,7 +60,7 @@ namespace Emby.Server.Implementations.Data
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();
}
[Conditional("DEBUG")]
private static void CheckName(string name)
{
#if DEBUG
throw new ArgumentException("Invalid param name: " + name, nameof(name));
#endif
}
public static void TryBind(this IStatement statement, string name, double value)

@ -687,7 +687,7 @@ namespace Emby.Server.Implementations.Data
if (item.Genres.Length > 0)
{
saveItemStatement.TryBind("@Genres", string.Join("|", item.Genres));
saveItemStatement.TryBind("@Genres", string.Join('|', item.Genres));
}
else
{
@ -749,7 +749,7 @@ namespace Emby.Server.Implementations.Data
if (item.LockedFields.Length > 0)
{
saveItemStatement.TryBind("@LockedFields", string.Join("|", item.LockedFields));
saveItemStatement.TryBind("@LockedFields", string.Join('|', item.LockedFields));
}
else
{
@ -758,7 +758,7 @@ namespace Emby.Server.Implementations.Data
if (item.Studios.Length > 0)
{
saveItemStatement.TryBind("@Studios", string.Join("|", item.Studios));
saveItemStatement.TryBind("@Studios", string.Join('|', item.Studios));
}
else
{
@ -785,7 +785,7 @@ namespace Emby.Server.Implementations.Data
if (item.Tags.Length > 0)
{
saveItemStatement.TryBind("@Tags", string.Join("|", item.Tags));
saveItemStatement.TryBind("@Tags", string.Join('|', item.Tags));
}
else
{
@ -807,7 +807,7 @@ namespace Emby.Server.Implementations.Data
if (item is Trailer trailer && trailer.TrailerTypes.Length > 0)
{
saveItemStatement.TryBind("@TrailerTypes", string.Join("|", trailer.TrailerTypes));
saveItemStatement.TryBind("@TrailerTypes", string.Join('|', trailer.TrailerTypes));
}
else
{
@ -902,7 +902,7 @@ namespace Emby.Server.Implementations.Data
if (item.ProductionLocations.Length > 0)
{
saveItemStatement.TryBind("@ProductionLocations", string.Join("|", item.ProductionLocations));
saveItemStatement.TryBind("@ProductionLocations", string.Join('|', item.ProductionLocations));
}
else
{
@ -911,7 +911,7 @@ namespace Emby.Server.Implementations.Data
if (item.ExtraIds.Length > 0)
{
saveItemStatement.TryBind("@ExtraIds", string.Join("|", item.ExtraIds));
saveItemStatement.TryBind("@ExtraIds", string.Join('|', item.ExtraIds));
}
else
{
@ -931,7 +931,7 @@ namespace Emby.Server.Implementations.Data
string artists = null;
if (item is IHasArtist hasArtists && hasArtists.Artists.Count > 0)
{
artists = string.Join("|", hasArtists.Artists);
artists = string.Join('|', hasArtists.Artists);
}
saveItemStatement.TryBind("@Artists", artists);
@ -940,7 +940,7 @@ namespace Emby.Server.Implementations.Data
if (item is IHasAlbumArtist hasAlbumArtists
&& hasAlbumArtists.AlbumArtists.Count > 0)
{
albumArtists = string.Join("|", hasAlbumArtists.AlbumArtists);
albumArtists = string.Join('|', hasAlbumArtists.AlbumArtists);
}
saveItemStatement.TryBind("@AlbumArtists", albumArtists);
@ -2549,7 +2549,7 @@ namespace Emby.Server.Implementations.Data
if (groups.Count > 0)
{
return " Group by " + string.Join(",", groups);
return " Group by " + string.Join(',', groups);
}
return string.Empty;
@ -2578,7 +2578,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count(distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@ -2630,7 +2630,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns))
+ string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
@ -2880,7 +2880,7 @@ namespace Emby.Server.Implementations.Data
}
var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, _retriveItemColumns))
+ string.Join(',', GetFinalColumnsToSelect(query, _retriveItemColumns))
+ GetFromText()
+ GetJoinUserDataText(query);
@ -2923,15 +2923,15 @@ namespace Emby.Server.Implementations.Data
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)
{
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
}
else
{
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
}
commandText += GetJoinUserDataText(query)
@ -3039,7 +3039,7 @@ namespace Emby.Server.Implementations.Data
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);
@ -3137,7 +3137,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }))
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@ -3203,7 +3203,7 @@ namespace Emby.Server.Implementations.Data
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);
if (whereClauses.Count != 0)
@ -3284,7 +3284,7 @@ namespace Emby.Server.Implementations.Data
var now = DateTime.UtcNow;
var commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "guid" }))
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "guid" }))
+ GetFromText()
+ GetJoinUserDataText(query);
@ -3327,15 +3327,15 @@ namespace Emby.Server.Implementations.Data
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)
{
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct SeriesPresentationUniqueKey)" })) + GetFromText();
}
else
{
commandText += " select " + string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
commandText += " select " + string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (guid)" })) + GetFromText();
}
commandText += GetJoinUserDataText(query)
@ -3596,7 +3596,7 @@ namespace Emby.Server.Implementations.Data
}
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})");
}
}
@ -3607,7 +3607,7 @@ namespace Emby.Server.Implementations.Data
}
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})");
}
@ -3618,7 +3618,7 @@ namespace Emby.Server.Implementations.Data
}
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})");
}
@ -4351,7 +4351,7 @@ namespace Emby.Server.Implementations.Data
}
else if (query.Years.Length > 1)
{
var val = string.Join(",", query.Years);
var val = string.Join(',', query.Years);
whereClauses.Add("ProductionYear in (" + val + ")");
}
@ -4401,7 +4401,7 @@ namespace Emby.Server.Implementations.Data
}
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 + ")");
}
@ -4498,7 +4498,7 @@ namespace Emby.Server.Implementations.Data
var paramName = "@HasAnyProviderId" + index;
// 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%
if (statement != null)
@ -4549,7 +4549,7 @@ namespace Emby.Server.Implementations.Data
}
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 + "))");
}
else
@ -4564,7 +4564,7 @@ namespace Emby.Server.Implementations.Data
}
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)
{
@ -4576,7 +4576,7 @@ namespace Emby.Server.Implementations.Data
}
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 + "))");
}
else
@ -4597,7 +4597,7 @@ namespace Emby.Server.Implementations.Data
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));
}
@ -5148,7 +5148,7 @@ AND Type = @InternalPersonType)");
}
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 + ")");
}
@ -5162,7 +5162,7 @@ AND Type = @InternalPersonType)");
}
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 + ")");
}
@ -5308,19 +5308,19 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ?
("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;
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 + "))";
}
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 + "))";
}
@ -5363,7 +5363,7 @@ AND Type = @InternalPersonType)");
var typeClause = itemValueTypes.Length == 1 ?
("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;
@ -5427,7 +5427,7 @@ AND Type = @InternalPersonType)");
columns = GetFinalColumnsToSelect(query, columns);
var commandText = "select "
+ string.Join(",", columns)
+ string.Join(',', columns)
+ GetFromText()
+ GetJoinUserDataText(query);
@ -5504,7 +5504,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
var countText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
@ -5565,7 +5565,7 @@ AND Type = @InternalPersonType)");
if (query.EnableTotalRecordCount)
{
commandText = "select "
+ string.Join(",", GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ string.Join(',', GetFinalColumnsToSelect(query, new[] { "count (distinct PresentationUniqueKey)" }))
+ GetFromText()
+ GetJoinUserDataText(query)
+ whereText;
@ -6207,9 +6207,9 @@ AND Type = @InternalPersonType)");
if (item.Type == MediaStreamType.Subtitle)
{
item.localizedUndefined = _localization.GetLocalizedString("Undefined");
item.localizedDefault = _localization.GetLocalizedString("Default");
item.localizedForced = _localization.GetLocalizedString("Forced");
item.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
item.LocalizedDefault = _localization.GetLocalizedString("Default");
item.LocalizedForced = _localization.GetLocalizedString("Forced");
}
return item;

@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.Data
connection.RunInTransaction(
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)",

@ -29,7 +29,7 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Mono.Nat" Version="3.0.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="DotNet.Glob" Version="3.1.2" />
</ItemGroup>

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

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

@ -35,8 +35,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
private DateTime _lastErrorResponse;
private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.GetOptions();
private DateTime _lastErrorResponse;
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
@ -111,7 +111,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
options.Headers.TryAddWithoutValidation("token", token);
using var response = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var dailySchedules = await 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);
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);
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 programIdsWithImages =
programDetails.Where(p => p.hasImageArtwork).Select(p => p.programID)
.ToList();
var programIdsWithImages = programDetails
.Where(p => p.hasImageArtwork).Select(p => p.programID)
.ToList();
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)
{
if (!string.IsNullOrWhiteSpace(image.height)
&& int.TryParse(image.height, out int value))
if (int.TryParse(image.height, out int value))
{
return value;
}
@ -704,7 +703,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
httpResponse.EnsureSuccessStatusCode();
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
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));
}
@ -776,7 +775,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, true, info, cancellationToken).ConfigureAwait(false);
await using var stream = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await 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("Mapping Stations to Channel");

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

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

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

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

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

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

@ -3,7 +3,7 @@
"Favorites": "Yêu Thích",
"Folders": "Thư Mục",
"Genres": "Thể Loại",
"HeaderAlbumArtists": "Bộ Sưu Tập Nghệ sĩ",
"HeaderAlbumArtists": "Tuyển Tập Nghệ sĩ",
"HeaderContinueWatching": "Xem Tiếp",
"HeaderLiveTV": "TV Trực Tiếp",
"Movies": "Phim",
@ -13,7 +13,7 @@
"Songs": "Các Bài Hát",
"Sync": "Đồng Bộ",
"ValueSpecialEpisodeName": "Đặc Biệt - {0}",
"Albums": "Albums",
"Albums": "Tuyển Tập",
"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ả.",
"TaskDownloadMissingSubtitles": "Tải Xuống Phụ Đề Bị Thiếu",

@ -166,7 +166,7 @@ namespace Emby.Server.Implementations.MediaEncoder
}
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;
break;
}

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

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

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

@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
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
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
@ -160,7 +160,7 @@ namespace Jellyfin.Api.Controllers
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
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)

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

@ -223,7 +223,7 @@ namespace Jellyfin.Api.Controllers
DeInterlace = true,
RequireNonAnamorphic = 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,
StreamOptions = new Dictionary<string, string>(),
EnableAdaptiveBitrateStreaming = true
@ -254,7 +254,7 @@ namespace Jellyfin.Api.Controllers
CopyTimestamps = true,
StartTimeTicks = startTimeTicks,
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
};

@ -113,14 +113,5 @@ namespace Jellyfin.Api.Extensions
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);
}
}

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

@ -39,7 +39,7 @@ namespace Jellyfin.Api.Helpers
}
// 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();
httpContext.Response.Headers[HeaderNames.AcceptRanges] = "none";

@ -523,7 +523,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="type">Dlna profile type.</param>
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)

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

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

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

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

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

@ -137,17 +137,14 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentNullException(nameof(user));
}
if (string.IsNullOrWhiteSpace(newName))
{
throw new ArgumentException("Invalid username", nameof(newName));
}
ThrowIfInvalidUsername(newName);
if (user.Username.Equals(newName, StringComparison.Ordinal))
{
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(
CultureInfo.InvariantCulture,
@ -201,9 +198,14 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
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();
@ -725,12 +727,22 @@ namespace Jellyfin.Server.Implementations.Users
_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)
{
// 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
// 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)

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

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

@ -32,7 +32,7 @@ namespace Jellyfin.Server.Migrations.Routines
public void Perform()
{
// 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)
{
_logger.LogInformation("Disabling transcoding throttling during migration");

@ -74,6 +74,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Dlna.Tests", "test
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.XbmcMetadata.Tests", "tests\Jellyfin.XbmcMetadata.Tests\Jellyfin.XbmcMetadata.Tests.csproj", "{30922383-D513-4F4D-B890-A940B57FA353}"
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
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -214,6 +220,7 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {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}
{FC1BC0CE-E8D2-4AE9-A6AB-8A02143B335D} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}

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

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

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

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

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

@ -1243,7 +1243,7 @@ namespace MediaBrowser.Controller.Entities
}
}
return string.Join("/", terms.ToArray());
return string.Join('/', terms.ToArray());
}
/// <summary>
@ -2795,7 +2795,7 @@ namespace MediaBrowser.Controller.Entities
{
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)

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

@ -131,6 +131,12 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
{
var videoStream = state.VideoStream;
if (videoStream == null)
{
// Remote stream doesn't have media info, disable vpp tonemapping.
return false;
}
var codec = videoStream.Codec;
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
@ -1381,7 +1387,8 @@ namespace MediaBrowser.Controller.MediaEncoding
var requestedProfile = requestedProfiles[0];
// 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 requestedScore = GetVideoProfileScore(requestedProfile);
@ -1710,7 +1717,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (filters.Count > 0)
{
return " -af \"" + string.Join(",", filters) + "\"";
return " -af \"" + string.Join(',', filters) + "\"";
}
return string.Empty;
@ -2530,7 +2537,7 @@ namespace MediaBrowser.Controller.MediaEncoding
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
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.RealFrameRate ?? 60) <= 30;
var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.AverageFrameRate ?? 60) <= 30;
var isScalingInAdvance = false;
var isCudaDeintInAdvance = false;
@ -2888,7 +2895,7 @@ namespace MediaBrowser.Controller.MediaEncoding
output += string.Format(
CultureInfo.InvariantCulture,
"{0}",
string.Join(",", filters));
string.Join(',', filters));
}
return output;
@ -3080,7 +3087,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier += " -deint 1";
if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.RealFrameRate ?? 60) > 30)
if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.AverageFrameRate ?? 60) > 30)
{
inputModifier += " -drop_second_field 1";
}
@ -3864,7 +3871,7 @@ namespace MediaBrowser.Controller.MediaEncoding
GetInputArgument(state, encodingOptions),
threads,
" -vn",
string.Join(" ", audioTranscodeParams),
string.Join(' ', audioTranscodeParams),
outputPath,
string.Empty,
string.Empty,

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

@ -103,7 +103,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public void SetFFmpegPath()
{
// 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
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
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
_configurationManager.SaveConfiguration("encoding", config);
@ -177,7 +177,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// Write the new ffmpeg path to the xml as <EncoderAppPath>
// This ensures its not lost on next startup
var config = _configurationManager.GetConfiguration<EncodingOptions>("encoding");
var config = _configurationManager.GetEncodingOptions();
config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config);

@ -24,6 +24,7 @@
<ItemGroup>
<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="System.Text.Encoding.CodePages" Version="5.0.0" />
<PackageReference Include="UTF.Unknown" Version="2.3.0" />

@ -681,9 +681,9 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.Type = MediaStreamType.Subtitle;
stream.Codec = NormalizeSubtitleCodec(stream.Codec);
stream.localizedUndefined = _localization.GetLocalizedString("Undefined");
stream.localizedDefault = _localization.GetLocalizedString("Default");
stream.localizedForced = _localization.GetLocalizedString("Forced");
stream.LocalizedUndefined = _localization.GetLocalizedString("Undefined");
stream.LocalizedDefault = _localization.GetLocalizedString("Default");
stream.LocalizedForced = _localization.GetLocalizedString("Forced");
}
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);
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
{
@ -1508,7 +1508,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (subtitle.Contains('.', StringComparison.Ordinal))
{
// 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
{

@ -1,130 +1,21 @@
#pragma warning disable CS1591
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
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");
/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
/// <summary>
/// Initializes a new instance of the <see cref="AssParser"/> class.
/// </summary>
/// <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;
}
}
}

@ -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 Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
public class SrtParser : ISubtitleParser
/// <summary>
/// SubRip subtitle parser.
/// </summary>
public class SrtParser : SubtitleEditParser<SubRip>
{
private readonly ILogger _logger;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public SrtParser(ILogger 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)
/// <summary>
/// Initializes a new instance of the <see cref="SrtParser"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
public SrtParser(ILogger logger) : base(logger)
{
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);
}
}
}

@ -1,477 +1,21 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using MediaBrowser.Model.MediaInfo;
#nullable enable
using Microsoft.Extensions.Logging;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace MediaBrowser.MediaEncoding.Subtitles
{
/// <summary>
/// <see href="https://github.com/SubtitleEdit/subtitleedit/blob/a299dc4407a31796364cc6ad83f0d3786194ba22/src/Logic/SubtitleFormats/SubStationAlpha.cs">Credit</see>.
/// SubStation Alpha subtitle parser.
/// </summary>
public class SsaParser : ISubtitleParser
public class SsaParser : SubtitleEditParser<SubStationAlpha>
{
/// <inheritdoc />
public SubtitleTrackInfo Parse(Stream stream, CancellationToken cancellationToken)
/// <summary>
/// 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('\\');
}
}
}
}
}
}

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

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

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

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

@ -5,6 +5,41 @@ namespace MediaBrowser.Model.Configuration
{
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 string TranscodingTempPath { get; set; }
@ -24,12 +59,12 @@ namespace MediaBrowser.Model.Configuration
public string HardwareAccelerationType { get; set; }
/// <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>
public string EncoderAppPath { get; set; }
/// <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>
public string EncoderAppPathDisplay { get; set; }
@ -76,40 +111,5 @@ namespace MediaBrowser.Model.Configuration
public bool EnableSubtitleExtraction { 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" };
}
}
}

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

@ -2,13 +2,30 @@
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Configuration
{
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 EnableRealtimeMonitor { get; set; }
@ -79,387 +96,5 @@ namespace MediaBrowser.Model.Configuration
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();
}
}

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

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

@ -10,6 +10,16 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
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[] DisabledMetadataSavers { get; set; }
@ -23,15 +33,5 @@ namespace MediaBrowser.Model.Configuration
public string[] DisabledImageFetchers { 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>();
}
}
}

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

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

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

@ -11,6 +11,24 @@ namespace MediaBrowser.Model.Configuration
/// </summary>
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>
/// Gets or sets the audio language preference.
/// </summary>
@ -52,23 +70,5 @@ namespace MediaBrowser.Model.Configuration
public bool RememberSubtitleSelections { 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>();
}
}
}

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

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

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

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

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

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

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

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

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

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

@ -7,31 +7,6 @@ namespace MediaBrowser.Model.Dlna
{
public class SearchCriteria
{
public SearchType SearchType { get; set; }
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <param name="limit">The limit.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term, int limit)
{
return new Regex(term).Split(str, limit);
}
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term)
{
return Regex.Split(str, term, RegexOptions.IgnoreCase);
}
public SearchCriteria(string search)
{
if (search.Length == 0)
@ -48,8 +23,8 @@ namespace MediaBrowser.Model.Dlna
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("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))
{
@ -71,5 +46,30 @@ namespace MediaBrowser.Model.Dlna
}
}
}
public SearchType SearchType { get; set; }
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <param name="limit">The limit.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term, int limit)
{
return new Regex(term).Split(str, limit);
}
/// <summary>
/// Splits the specified string.
/// </summary>
/// <param name="str">The string.</param>
/// <param name="term">The term.</param>
/// <returns>System.String[].</returns>
private static string[] RegexSplit(string str, string term)
{
return Regex.Split(str, term, RegexOptions.IgnoreCase);
}
}
}

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

@ -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))
{
@ -274,14 +274,14 @@ namespace MediaBrowser.Model.Dlna
if (options.ForceDirectPlay)
{
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;
}
if (options.ForceDirectStream)
{
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;
}
@ -349,7 +349,7 @@ namespace MediaBrowser.Model.Dlna
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;
}
@ -698,7 +698,7 @@ namespace MediaBrowser.Model.Dlna
if (directPlay != null)
{
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)
{
@ -1404,7 +1404,9 @@ namespace MediaBrowser.Model.Dlna
{
_logger.LogInformation(
"Bitrate exceeds {PlayBackMethod} limit: media bitrate: {MediaBitrate}, max bitrate: {MaxBitrate}",
playMethod, itemBitrate, requestedMaxBitrate);
playMethod,
itemBitrate,
requestedMaxBitrate);
return false;
}
@ -1733,7 +1735,7 @@ namespace MediaBrowser.Model.Dlna
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

@ -294,13 +294,13 @@ namespace MediaBrowser.Model.Dto
public NameGuidPair[] GenreItems { get; set; }
/// <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>
/// <value>The parent logo item id.</value>
public string ParentLogoItemId { get; set; }
/// <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>
/// <value>The parent backdrop item id.</value>
public string ParentBackdropItemId { get; set; }
@ -318,7 +318,7 @@ namespace MediaBrowser.Model.Dto
public int? LocalTrailerCount { get; set; }
/// <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>
/// <value>The user data.</value>
public UserItemDataDto UserData { get; set; }
@ -506,7 +506,7 @@ namespace MediaBrowser.Model.Dto
public string ParentLogoImageTag { get; set; }
/// <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>
/// <value>The parent art item id.</value>
public string ParentArtItemId { get; set; }
@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dto
public string ChannelPrimaryImageTag { get; set; }
/// <summary>
/// The start date of the recording, in UTC.
/// Gets or sets the start date of the recording, in UTC.
/// </summary>
public DateTime? StartDate { get; set; }

@ -12,6 +12,18 @@ namespace MediaBrowser.Model.Dto
{
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 string Id { get; set; }
@ -31,6 +43,7 @@ namespace MediaBrowser.Model.Dto
public string Name { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the media is remote.
/// Differentiate internet url vs local network.
/// </summary>
public bool IsRemote { get; set; }
@ -95,16 +108,28 @@ namespace MediaBrowser.Model.Dto
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>();
MediaStreams = new List<MediaStream>();
MediaAttachments = Array.Empty<MediaAttachment>();
RequiredHttpHeaders = new Dictionary<string, string>();
SupportsTranscoding = true;
SupportsDirectStream = true;
SupportsDirectPlay = true;
SupportsProbing = true;
get
{
foreach (var i in MediaStreams)
{
if (i.Type == MediaStreamType.Video)
{
return i;
}
}
return null;
}
}
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)
{
if (defaultIndex.HasValue)
@ -175,23 +193,6 @@ namespace MediaBrowser.Model.Dto
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)
{
foreach (var i in MediaStreams)

@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
{
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 CountryInfo[] Countries { get; set; }
@ -21,14 +30,5 @@ namespace MediaBrowser.Model.Dto
public string ContentType { 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>();
}
}
}

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

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

@ -10,6 +10,15 @@ namespace MediaBrowser.Model.Dto
/// </summary>
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>
/// Gets or sets the name.
/// </summary>
@ -94,15 +103,6 @@ namespace MediaBrowser.Model.Dto
/// <value>The primary image aspect ratio.</value>
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 />
public override string ToString()
{

@ -24,36 +24,4 @@ namespace MediaBrowser.Model.Entities
public const string Playlists = "playlists";
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";
}
}

@ -84,7 +84,7 @@ namespace MediaBrowser.Model.Entities
public string Title { get; set; }
/// <summary>
/// Gets or sets the video range.
/// Gets the video range.
/// </summary>
/// <value>The video range.</value>
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
{
@ -154,7 +154,7 @@ namespace MediaBrowser.Model.Entities
if (IsDefault)
{
attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
}
if (!string.IsNullOrEmpty(Title))
@ -211,7 +211,7 @@ namespace MediaBrowser.Model.Entities
return result.ToString();
}
return string.Join(" ", attributes);
return string.Join(' ', attributes);
}
case MediaStreamType.Subtitle:
@ -229,17 +229,17 @@ namespace MediaBrowser.Model.Entities
}
else
{
attributes.Add(string.IsNullOrEmpty(localizedUndefined) ? "Und" : localizedUndefined);
attributes.Add(string.IsNullOrEmpty(LocalizedUndefined) ? "Und" : LocalizedUndefined);
}
if (IsDefault)
{
attributes.Add(string.IsNullOrEmpty(localizedDefault) ? "Default" : localizedDefault);
attributes.Add(string.IsNullOrEmpty(LocalizedDefault) ? "Default" : LocalizedDefault);
}
if (IsForced)
{
attributes.Add(string.IsNullOrEmpty(localizedForced) ? "Forced" : localizedForced);
attributes.Add(string.IsNullOrEmpty(LocalizedForced) ? "Forced" : LocalizedForced);
}
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; }
/// <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)
{
string codec = format ?? string.Empty;
@ -533,35 +562,5 @@ namespace MediaBrowser.Model.Entities
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; }
}
}

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

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

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

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

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

Loading…
Cancel
Save