aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile23
-rw-r--r--Emby.Dlna/PlayTo/PlayToManager.cs24
-rw-r--r--Emby.Dlna/Properties/AssemblyInfo.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthService.cs9
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs85
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ro.json5
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs14
-rw-r--r--Jellyfin.Api/Auth/CustomAuthenticationHandler.cs5
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs2
-rw-r--r--Jellyfin.Server.Implementations/ModelBuilderExtensions.cs48
-rw-r--r--Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs21
-rw-r--r--Jellyfin.Server/Startup.cs2
-rw-r--r--MediaBrowser.Common/Json/Converters/JsonDateTimeIso8601Converter.cs24
-rw-r--r--MediaBrowser.Common/Json/JsonDefaults.cs1
-rw-r--r--MediaBrowser.Controller/Net/AuthorizationInfo.cs5
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs19
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs6
-rw-r--r--MediaBrowser.sln9
-rw-r--r--tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs4
-rw-r--r--tests/Jellyfin.Dlna.Tests/GetUuidTests.cs17
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj33
23 files changed, 260 insertions, 104 deletions
diff --git a/Dockerfile b/Dockerfile
index 963027b49..41dd3d081 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -27,8 +27,15 @@ ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
+
+# https://github.com/intel/compute-runtime/releases
+ARG GMMLIB_VERSION=20.3.2
+ARG IGC_VERSION=1.0.5435
+ARG NEO_VERSION=20.46.18421
+ARG LEVEL_ZERO_VERSION=1.0.18421
+
# Install dependencies:
-# mesa-va-drivers: needed for AMD VAAPI
+# mesa-va-drivers: needed for AMD VAAPI. Mesa >= 20.1 is required for HEVC transcoding.
RUN apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
&& wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
@@ -39,6 +46,20 @@ RUN apt-get update \
jellyfin-ffmpeg \
openssl \
locales \
+# Intel VAAPI Tone mapping dependencies:
+# Prefer NEO to Beignet since the latter one doesn't support Comet Lake or newer for now.
+# Do not use the intel-opencl-icd package from repo since they will not build with RELEASE_WITH_REGKEYS enabled.
+ && mkdir intel-compute-runtime \
+ && cd intel-compute-runtime \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-gmmlib_${GMMLIB_VERSION}_amd64.deb \
+ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-core_${IGC_VERSION}_amd64.deb \
+ && wget https://github.com/intel/intel-graphics-compiler/releases/download/igc-${IGC_VERSION}/intel-igc-opencl_${IGC_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-opencl_${NEO_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-ocloc_${NEO_VERSION}_amd64.deb \
+ && wget https://github.com/intel/compute-runtime/releases/download/${NEO_VERSION}/intel-level-zero-gpu_${LEVEL_ZERO_VERSION}_amd64.deb \
+ && dpkg -i *.deb \
+ && cd .. \
+ && rm -rf intel-compute-runtime \
&& apt-get remove gnupg wget apt-transport-https -y \
&& apt-get clean autoclean -y \
&& apt-get autoremove -y \
diff --git a/Emby.Dlna/PlayTo/PlayToManager.cs b/Emby.Dlna/PlayTo/PlayToManager.cs
index 854b6a1a2..cb183ce71 100644
--- a/Emby.Dlna/PlayTo/PlayToManager.cs
+++ b/Emby.Dlna/PlayTo/PlayToManager.cs
@@ -3,13 +3,11 @@
using System;
using System.Globalization;
using System.Linq;
-using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Events;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -130,24 +128,36 @@ namespace Emby.Dlna.PlayTo
}
}
- private static string GetUuid(string usn)
+ internal static string GetUuid(string usn)
{
const string UuidStr = "uuid:";
const string UuidColonStr = "::";
var index = usn.IndexOf(UuidStr, StringComparison.OrdinalIgnoreCase);
+ if (index == -1)
+ {
+ return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ }
+
+ ReadOnlySpan<char> tmp = usn.AsSpan()[(index + UuidStr.Length)..];
+
+ index = tmp.IndexOf(UuidColonStr, StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- return usn.Substring(index + UuidStr.Length);
+ tmp = tmp[..index];
}
- index = usn.IndexOf(UuidColonStr, StringComparison.OrdinalIgnoreCase);
+ index = tmp.IndexOf('{');
if (index != -1)
{
- usn = usn.Substring(0, index + UuidColonStr.Length);
+ int endIndex = tmp.IndexOf('}');
+ if (endIndex != -1)
+ {
+ tmp = tmp[(index + 1)..endIndex];
+ }
}
- return usn.GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ return tmp.ToString();
}
private async Task AddDevice(UpnpDeviceInfo info, string location, CancellationToken cancellationToken)
diff --git a/Emby.Dlna/Properties/AssemblyInfo.cs b/Emby.Dlna/Properties/AssemblyInfo.cs
index a2c1e0db8..606ffcf4f 100644
--- a/Emby.Dlna/Properties/AssemblyInfo.cs
+++ b/Emby.Dlna/Properties/AssemblyInfo.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Resources;
+using System.Runtime.CompilerServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
@@ -13,6 +14,7 @@ using System.Resources;
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
+[assembly: InternalsVisibleTo("Jellyfin.Dlna.Tests")]
// Version information for an assembly consists of the following four values:
//
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
index df7a034e8..4a0fc8239 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Net;
@@ -20,9 +21,15 @@ namespace Emby.Server.Implementations.HttpServer.Security
public AuthorizationInfo Authenticate(HttpRequest request)
{
var auth = _authorizationContext.GetAuthorizationInfo(request);
+
+ if (!auth.HasToken)
+ {
+ throw new AuthenticationException("Request does not contain a token.");
+ }
+
if (!auth.IsAuthenticated)
{
- throw new AuthenticationException("Invalid token.");
+ throw new SecurityException("Invalid token.");
}
if (auth.User?.HasPermission(PermissionKind.IsDisabled) ?? false)
diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index fdf2e3908..d62e2eefe 100644
--- a/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -102,7 +102,8 @@ namespace Emby.Server.Implementations.HttpServer.Security
DeviceId = deviceId,
Version = version,
Token = token,
- IsAuthenticated = false
+ IsAuthenticated = false,
+ HasToken = false
};
if (string.IsNullOrWhiteSpace(token))
@@ -111,6 +112,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
return authInfo;
}
+ authInfo.HasToken = true;
var result = _authRepo.Get(new AuthenticationInfoQuery
{
AccessToken = token
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index f181eb7a0..1084ddf74 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -4,7 +4,6 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
@@ -19,7 +18,6 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.LiveTv;
-using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
@@ -36,6 +34,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly IApplicationHost _appHost;
private readonly ICryptoProvider _cryptoProvider;
+ private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
+ private DateTime _lastErrorResponse;
+
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
IJsonSerializer jsonSerializer,
@@ -50,8 +51,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
_cryptoProvider = cryptoProvider;
}
- private string UserAgent => _appHost.ApplicationUserAgent;
-
/// <inheritdoc />
public string Name => "Schedules Direct";
@@ -307,7 +306,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
if (details.contentRating != null && details.contentRating.Count > 0)
{
- info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-").Replace("--", "-");
+ info.OfficialRating = details.contentRating[0].code.Replace("TV", "TV-", StringComparison.Ordinal)
+ .Replace("--", "-", StringComparison.Ordinal);
var invalid = new[] { "N/A", "Approved", "Not Rated", "Passed" };
if (invalid.Contains(info.OfficialRating, StringComparer.OrdinalIgnoreCase))
@@ -450,7 +450,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private async Task<List<ScheduleDirect.ShowImages>> GetImageForPrograms(
ListingsProviderInfo info,
- List<string> programIds,
+ IReadOnlyList<string> programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
@@ -458,23 +458,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return new List<ScheduleDirect.ShowImages>();
}
- var imageIdString = "[";
-
- foreach (var i in programIds)
+ StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
+ foreach (ReadOnlySpan<char> i in programIds)
{
- var imageId = i.Substring(0, 10);
-
- if (!imageIdString.Contains(imageId, StringComparison.Ordinal))
- {
- imageIdString += "\"" + imageId + "\",";
- }
+ str.Append('"')
+ .Append(i.Slice(0, 10))
+ .Append("\",");
}
- imageIdString = imageIdString.TrimEnd(',') + "]";
+ // Remove last ,
+ str.Length--;
+ str.Append(']');
using var message = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/metadata/programs")
{
- Content = new StringContent(imageIdString, Encoding.UTF8, MediaTypeNames.Application.Json)
+ Content = new StringContent(str.ToString(), Encoding.UTF8, MediaTypeNames.Application.Json)
};
try
@@ -539,9 +537,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return lineups;
}
- private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
- private DateTime _lastErrorResponse;
-
private async Task<string> GetToken(ListingsProviderInfo info, CancellationToken cancellationToken)
{
var username = info.Username;
@@ -564,8 +559,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return null;
}
- NameValuePair savedToken;
- if (!_tokens.TryGetValue(username, out savedToken))
+ if (!_tokens.TryGetValue(username, out NameValuePair savedToken))
{
savedToken = new NameValuePair();
_tokens.TryAdd(username, savedToken);
@@ -655,7 +649,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = await Send(options, false, null, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await _jsonSerializer.DeserializeFromStreamAsync<ScheduleDirect.Token>(stream).ConfigureAwait(false);
- if (root.message == "OK")
+ if (string.Equals(root.message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: " + root.token);
return root.token;
@@ -779,24 +773,28 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var options = new HttpRequestMessage(HttpMethod.Get, ApiUrl + "/lineups/" + listingsId);
options.Headers.TryAddWithoutValidation("token", token);
- var list = new List<ChannelInfo>();
-
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.DeserializeFromStreamAsync<ScheduleDirect.Channel>(stream).ConfigureAwait(false);
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.map.Count);
_logger.LogInformation("Mapping Stations to Channel");
- var allStations = root.stations ?? Enumerable.Empty<ScheduleDirect.Station>();
+ var allStations = root.stations ?? new List<ScheduleDirect.Station>();
- foreach (ScheduleDirect.Map map in root.map)
+ var map = root.map;
+ int len = map.Count;
+ var array = new List<ChannelInfo>(len);
+ for (int i = 0; i < len; i++)
{
- var channelNumber = GetChannelNumber(map);
+ var channelNumber = GetChannelNumber(map[i]);
- var station = allStations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
+ var station = allStations.Find(item => string.Equals(item.stationID, map[i].stationID, StringComparison.OrdinalIgnoreCase));
if (station == null)
{
- station = new ScheduleDirect.Station { stationID = map.stationID };
+ station = new ScheduleDirect.Station
+ {
+ stationID = map[i].stationID
+ };
}
var channelInfo = new ChannelInfo
@@ -812,32 +810,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
channelInfo.ImageUrl = station.logo.URL;
}
- list.Add(channelInfo);
- }
-
- return list;
- }
-
- private ScheduleDirect.Station GetStation(List<ScheduleDirect.Station> allStations, string channelNumber, string channelName)
- {
- if (!string.IsNullOrWhiteSpace(channelName))
- {
- channelName = NormalizeName(channelName);
-
- var result = allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
-
- if (result != null)
- {
- return result;
- }
- }
-
- if (!string.IsNullOrWhiteSpace(channelNumber))
- {
- return allStations.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
+ array[i] = channelInfo;
}
- return null;
+ return array;
}
private static string NormalizeName(string value)
@@ -1046,7 +1022,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- //
public class Title
{
public string title120 { get; set; }
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index dcf3311cd..23d45b473 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -114,7 +114,7 @@
"TaskUpdatePluginsDescription": "Κατεβάζει και εγκαθιστά ενημερώσεις για τις προσθήκες που έχουν ρυθμιστεί για αυτόματη ενημέρωση.",
"TaskUpdatePlugins": "Ενημέρωση Προσθηκών",
"TaskRefreshPeopleDescription": "Ενημερώνει μεταδεδομένα για ηθοποιούς και σκηνοθέτες στην βιβλιοθήκη των πολυμέσων σας.",
- "TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής δραστηριοτήτων παλαιότερες από την ηλικία που έχει διαμορφωθεί.",
+ "TaskCleanActivityLogDescription": "Διαγράφει καταχωρήσεις απο το αρχείο καταγραφής παλαιότερες από την επιλεγμένη ηλικία.",
"TaskCleanActivityLog": "Καθαρό Αρχείο Καταγραφής Δραστηριοτήτων",
"Undefined": "Απροσδιόριστο",
"Forced": "Εξαναγκασμένο",
diff --git a/Emby.Server.Implementations/Localization/Core/ro.json b/Emby.Server.Implementations/Localization/Core/ro.json
index 5e4022292..510aac11c 100644
--- a/Emby.Server.Implementations/Localization/Core/ro.json
+++ b/Emby.Server.Implementations/Localization/Core/ro.json
@@ -114,5 +114,8 @@
"TasksLibraryCategory": "Librărie",
"TasksMaintenanceCategory": "Mentenanță",
"TaskCleanActivityLogDescription": "Șterge intrările din jurnalul de activitate mai vechi de data configurată.",
- "TaskCleanActivityLog": "Curăță Jurnalul de Activitate"
+ "TaskCleanActivityLog": "Curăță Jurnalul de Activitate",
+ "Undefined": "Nedefinit",
+ "Forced": "Forțat",
+ "Default": "Implicit"
}
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index a697c6476..3cfcecbd1 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -192,7 +192,7 @@ namespace Emby.Server.Implementations.TV
Func<Episode> getEpisode = () =>
{
- return _libraryManager.GetItemList(new InternalItemsQuery(user)
+ var nextEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
@@ -205,6 +205,18 @@ namespace Emby.Server.Implementations.TV
MinSortName = lastWatchedEpisode?.SortName,
DtoOptions = dtoOptions
}).Cast<Episode>().FirstOrDefault();
+
+ if (nextEpisode != null)
+ {
+ var userData = _userDataManager.GetUserData(user, nextEpisode);
+
+ if (userData.PlaybackPositionTicks > 0)
+ {
+ return null;
+ }
+ }
+
+ return nextEpisode;
};
if (lastWatchedEpisode != null)
diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
index 27a1f61be..c56233794 100644
--- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
+++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs
@@ -18,6 +18,7 @@ namespace Jellyfin.Api.Auth
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IAuthService _authService;
+ private readonly ILogger<CustomAuthenticationHandler> _logger;
/// <summary>
/// Initializes a new instance of the <see cref="CustomAuthenticationHandler" /> class.
@@ -35,6 +36,7 @@ namespace Jellyfin.Api.Auth
ISystemClock clock) : base(options, logger, encoder, clock)
{
_authService = authService;
+ _logger = logger.CreateLogger<CustomAuthenticationHandler>();
}
/// <inheritdoc />
@@ -70,7 +72,8 @@ namespace Jellyfin.Api.Auth
}
catch (AuthenticationException ex)
{
- return Task.FromResult(AuthenticateResult.Fail(ex));
+ _logger.LogDebug(ex, "Error authenticating with {Handler}", nameof(CustomAuthenticationHandler));
+ return Task.FromResult(AuthenticateResult.NoResult());
}
catch (SecurityException ex)
{
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index 45e71f16e..bf8818f8d 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Interfaces;
@@ -140,6 +141,7 @@ namespace Jellyfin.Server.Implementations
/// <inheritdoc />
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ modelBuilder.SetDefaultDateTimeKind(DateTimeKind.Utc);
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("jellyfin");
diff --git a/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs b/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs
new file mode 100644
index 000000000..80ad65a42
--- /dev/null
+++ b/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs
@@ -0,0 +1,48 @@
+using System;
+using Jellyfin.Server.Implementations.ValueConverters;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Jellyfin.Server.Implementations
+{
+ /// <summary>
+ /// Model builder extensions.
+ /// </summary>
+ public static class ModelBuilderExtensions
+ {
+ /// <summary>
+ /// Specify value converter for the object type.
+ /// </summary>
+ /// <param name="modelBuilder">The model builder.</param>
+ /// <param name="converter">The <see cref="ValueConverter{TModel,TProvider}"/>.</param>
+ /// <typeparam name="T">The type to convert.</typeparam>
+ /// <returns>The modified <see cref="ModelBuilder"/>.</returns>
+ public static ModelBuilder UseValueConverterForType<T>(this ModelBuilder modelBuilder, ValueConverter converter)
+ {
+ var type = typeof(T);
+ foreach (var entityType in modelBuilder.Model.GetEntityTypes())
+ {
+ foreach (var property in entityType.GetProperties())
+ {
+ if (property.ClrType == type)
+ {
+ property.SetValueConverter(converter);
+ }
+ }
+ }
+
+ return modelBuilder;
+ }
+
+ /// <summary>
+ /// Specify the default <see cref="DateTimeKind"/>.
+ /// </summary>
+ /// <param name="modelBuilder">The model builder to extend.</param>
+ /// <param name="kind">The <see cref="DateTimeKind"/> to specify.</param>
+ public static void SetDefaultDateTimeKind(this ModelBuilder modelBuilder, DateTimeKind kind)
+ {
+ modelBuilder.UseValueConverterForType<DateTime>(new DateTimeKindValueConverter(kind));
+ modelBuilder.UseValueConverterForType<DateTime?>(new DateTimeKindValueConverter(kind));
+ }
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs b/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs
new file mode 100644
index 000000000..8a510898b
--- /dev/null
+++ b/Jellyfin.Server.Implementations/ValueConverters/DateTimeKindValueConverter.cs
@@ -0,0 +1,21 @@
+using System;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace Jellyfin.Server.Implementations.ValueConverters
+{
+ /// <summary>
+ /// ValueConverter to specify kind.
+ /// </summary>
+ public class DateTimeKindValueConverter : ValueConverter<DateTime, DateTime>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DateTimeKindValueConverter"/> class.
+ /// </summary>
+ /// <param name="kind">The kind to specify.</param>
+ /// <param name="mappingHints">The mapping hints.</param>
+ public DateTimeKindValueConverter(DateTimeKind kind, ConverterMappingHints mappingHints = null)
+ : base(v => v.ToUniversalTime(), v => DateTime.SpecifyKind(v, kind), mappingHints)
+ {
+ }
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 4ffcb940f..f144dd12e 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -81,7 +81,7 @@ namespace Jellyfin.Server
{
c.DefaultRequestHeaders.UserAgent.Add(productHeader);
c.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue($"({_serverApplicationHost.ApplicationUserAgentAddress})"));
- c.DefaultRequestHeaders.Accept.Add(acceptJsonHeader);
+ c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
})
.ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
diff --git a/MediaBrowser.Common/Json/Converters/JsonDateTimeIso8601Converter.cs b/MediaBrowser.Common/Json/Converters/JsonDateTimeIso8601Converter.cs
deleted file mode 100644
index 63b344a9d..000000000
--- a/MediaBrowser.Common/Json/Converters/JsonDateTimeIso8601Converter.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System;
-using System.Globalization;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-
-namespace MediaBrowser.Common.Json.Converters
-{
- /// <summary>
- /// Returns an ISO8601 formatted datetime.
- /// </summary>
- /// <remarks>
- /// Used for legacy compatibility.
- /// </remarks>
- public class JsonDateTimeIso8601Converter : JsonConverter<DateTime>
- {
- /// <inheritdoc />
- public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
- => reader.GetDateTime();
-
- /// <inheritdoc />
- public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
- => writer.WriteStringValue(value.ToString("O", CultureInfo.InvariantCulture));
- }
-}
diff --git a/MediaBrowser.Common/Json/JsonDefaults.cs b/MediaBrowser.Common/Json/JsonDefaults.cs
index 1aaf4a43b..c5050a21d 100644
--- a/MediaBrowser.Common/Json/JsonDefaults.cs
+++ b/MediaBrowser.Common/Json/JsonDefaults.cs
@@ -43,7 +43,6 @@ namespace MediaBrowser.Common.Json
options.Converters.Add(new JsonVersionConverter());
options.Converters.Add(new JsonStringEnumConverter());
options.Converters.Add(new JsonNullableStructConverterFactory());
- options.Converters.Add(new JsonDateTimeIso8601Converter());
return options;
}
diff --git a/MediaBrowser.Controller/Net/AuthorizationInfo.cs b/MediaBrowser.Controller/Net/AuthorizationInfo.cs
index 0194c596f..93573e08e 100644
--- a/MediaBrowser.Controller/Net/AuthorizationInfo.cs
+++ b/MediaBrowser.Controller/Net/AuthorizationInfo.cs
@@ -58,5 +58,10 @@ namespace MediaBrowser.Controller.Net
/// Gets or sets a value indicating whether the token is authenticated.
/// </summary>
public bool IsAuthenticated { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the request has a token.
+ /// </summary>
+ public bool HasToken { get; set; }
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 5f60c09ae..753da46a6 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -36,7 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <summary>
/// The default image extraction timeout in milliseconds.
/// </summary>
- internal const int DefaultImageExtractionTimeout = 5000;
+ internal const int DefaultImageExtractionTimeout = 10000;
/// <summary>
/// The us culture.
@@ -553,12 +553,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
var mapArg = imageStreamIndex.HasValue ? (" -map 0:v:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
- var enableThumbnail = !new List<string> { "wtv" }.Contains(container ?? string.Empty, StringComparer.OrdinalIgnoreCase);
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
- var thumbnail = enableThumbnail ? ",thumbnail=24" : string.Empty;
+ var enableThumbnail = useIFrame && !string.Equals("wtv", container, StringComparison.OrdinalIgnoreCase);
+ if (enableThumbnail)
+ {
+ if (string.IsNullOrEmpty(vf))
+ {
+ vf = "-vf thumbnail=24";
+ }
+ else
+ {
+ vf += ",thumbnail=24";
+ }
+ }
- var args = useIFrame ? string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {5} -v quiet -vframes 1 {2}{4} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, thumbnail, threads) :
- string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, threads);
var probeSizeArgument = EncodingHelper.GetProbeSizeArgument(1);
var analyzeDurationArgument = EncodingHelper.GetAnalyzeDurationArgument(1);
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 97d61441c..bd026bce1 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -778,7 +778,11 @@ namespace MediaBrowser.MediaEncoding.Probing
}
}
- if (bitrate == 0 && formatInfo != null && !string.IsNullOrEmpty(formatInfo.BitRate) && stream.Type == MediaStreamType.Video)
+ // The bitrate info of FLAC musics and some videos is included in formatInfo.
+ if (bitrate == 0
+ && formatInfo != null
+ && !string.IsNullOrEmpty(formatInfo.BitRate)
+ && (stream.Type == MediaStreamType.Video || (isAudio && stream.Type == MediaStreamType.Audio)))
{
// If the stream info doesn't have a bitrate get the value from the media format info
if (int.TryParse(formatInfo.BitRate, NumberStyles.Any, _usCulture, out var value))
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index cb204137b..5a807372d 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -1,4 +1,4 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30503.244
MinimumVisualStudioVersion = 10.0.40219.1
@@ -70,6 +70,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking", "Jell
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jellyfin.Networking.Tests", "tests\Jellyfin.Networking.Tests\NetworkTesting\Jellyfin.Networking.Tests.csproj", "{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jellyfin.Dlna.Tests", "tests\Jellyfin.Dlna.Tests\Jellyfin.Dlna.Tests.csproj", "{B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -188,6 +190,10 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -200,6 +206,7 @@ Global
{2E3A1B4B-4225-4AAA-8B29-0181A84E7AEE} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{462584F7-5023-4019-9EAC-B98CA458C0A0} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
+ {B8AE4B9D-E8D3-4B03-A95E-7FD8CECECC50} = {FBBB5129-006E-4AD7-BAD5-8B7CA1D10ED6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3448830C-EBDC-426C-85CD-7BBB9651A7FE}
diff --git a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
index 90c491666..ee20cc573 100644
--- a/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
+++ b/tests/Jellyfin.Api.Tests/Auth/CustomAuthenticationHandlerTests.cs
@@ -69,7 +69,7 @@ namespace Jellyfin.Api.Tests.Auth
}
[Fact]
- public async Task HandleAuthenticateAsyncShouldFailOnAuthenticationException()
+ public async Task HandleAuthenticateAsyncShouldProvideNoResultOnAuthenticationException()
{
var errorMessage = _fixture.Create<string>();
@@ -81,7 +81,7 @@ namespace Jellyfin.Api.Tests.Auth
var authenticateResult = await _sut.AuthenticateAsync();
Assert.False(authenticateResult.Succeeded);
- Assert.Equal(errorMessage, authenticateResult.Failure?.Message);
+ Assert.True(authenticateResult.None);
}
[Fact]
diff --git a/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs b/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs
new file mode 100644
index 000000000..7655e3f7c
--- /dev/null
+++ b/tests/Jellyfin.Dlna.Tests/GetUuidTests.cs
@@ -0,0 +1,17 @@
+using Emby.Dlna.PlayTo;
+using Xunit;
+
+namespace Jellyfin.Dlna.Tests
+{
+ public static class GetUuidTests
+ {
+ [Theory]
+ [InlineData("uuid:fc4ec57e-b051-11db-88f8-0060085db3f6::urn:schemas-upnp-org:device:WANDevice:1", "fc4ec57e-b051-11db-88f8-0060085db3f6")]
+ [InlineData("uuid:IGD{8c80f73f-4ba0-45fa-835d-042505d052be}000000000000", "8c80f73f-4ba0-45fa-835d-042505d052be")]
+ [InlineData("uuid:IGD{8c80f73f-4ba0-45fa-835d-042505d052be}000000000000::urn:schemas-upnp-org:device:InternetGatewayDevice:1", "8c80f73f-4ba0-45fa-835d-042505d052be")]
+ [InlineData("uuid:00000000-0000-0000-0000-000000000000::upnp:rootdevice", "00000000-0000-0000-0000-000000000000")]
+ [InlineData("uuid:fc4ec57e-b051-11db-88f8-0060085db3f6", "fc4ec57e-b051-11db-88f8-0060085db3f6")]
+ public static void GetUuid_Valid_Success(string usn, string uuid)
+ => Assert.Equal(uuid, PlayToManager.GetUuid(usn));
+ }
+}
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
new file mode 100644
index 000000000..f91db6744
--- /dev/null
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -0,0 +1,33 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <TargetFramework>net5.0</TargetFramework>
+ <IsPackable>false</IsPackable>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <Nullable>enable</Nullable>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.0" />
+ <PackageReference Include="xunit" Version="2.4.1" />
+ <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
+ <PackageReference Include="coverlet.collector" Version="1.3.0" />
+ </ItemGroup>
+
+ <!-- Code Analyzers -->
+ <ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="../../Emby.Dlna/Emby.Dlna.csproj" />
+ </ItemGroup>
+
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <CodeAnalysisRuleSet>../jellyfin-tests.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+
+</Project>