aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Emby.Dlna/DlnaManager.cs9
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj2
-rw-r--r--Emby.Drawing/ImageProcessor.cs2
-rw-r--r--Emby.Server.Implementations/AppBase/ConfigurationHelper.cs9
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj12
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs99
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs14
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs17
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs11
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs22
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs18
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs20
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs8
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs11
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs49
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs21
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs12
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs7
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs19
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs4
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs21
-rw-r--r--Emby.Server.Implementations/Localization/Core/ar.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/cy.json14
-rw-r--r--Emby.Server.Implementations/Localization/Core/en-GB.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/eo.json94
-rw-r--r--Emby.Server.Implementations/Localization/Core/et.json118
-rw-r--r--Emby.Server.Implementations/Localization/Core/nb.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/pr.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/uk.json10
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs31
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs2
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs6
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj8
-rw-r--r--Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs5
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj2
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj8
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj10
-rw-r--r--MediaBrowser.Common/FfmpegException.cs (renamed from MediaBrowser.MediaEncoding/FfmpegException.cs)2
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj8
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs3
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs2
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs2
-rw-r--r--MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs12
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs42
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj4
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs3
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs2
-rw-r--r--MediaBrowser.Model/IO/AsyncFile.cs19
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj4
-rw-r--r--MediaBrowser.Providers/Manager/ImageSaver.cs6
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs3
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj6
-rw-r--r--MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs220
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs1
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs8
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs1
-rw-r--r--MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs47
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs7
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs6
-rw-r--r--MediaBrowser.Providers/Studios/StudiosImageProvider.cs7
-rw-r--r--MediaBrowser.Providers/Subtitles/SubtitleManager.cs11
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs11
-rw-r--r--deployment/Dockerfile.centos.amd642
-rw-r--r--deployment/Dockerfile.fedora.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.amd642
-rw-r--r--deployment/Dockerfile.ubuntu.arm642
-rw-r--r--deployment/Dockerfile.ubuntu.armhf2
-rw-r--r--src/Jellyfin.Extensions/ReadOnlyListExtension.cs61
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj4
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs68
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json260
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs216
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs176
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs240
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json1
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json1
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj4
110 files changed, 1923 insertions, 431 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index cb52cafed..5c4a031bc 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -149,6 +149,7 @@
- [skyfrk](https://github.com/skyfrk)
- [ianjazz246](https://github.com/ianjazz246)
- [peterspenler](https://github.com/peterspenler)
+ - [MBR-0001](https://github.com/MBR-0001)
# Emby Contributors
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index 8fe9d484e..73e8a0008 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -359,14 +359,17 @@ namespace Emby.Dlna
// The stream should exist as we just got its name from GetManifestResourceNames
using (var stream = _assembly.GetManifestResourceStream(name)!)
{
+ var length = stream.Length;
var fileInfo = _fileSystem.GetFileInfo(path);
- if (!fileInfo.Exists || fileInfo.Length != stream.Length)
+ if (!fileInfo.Exists || fileInfo.Length != length)
{
Directory.CreateDirectory(systemProfilesPath);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = length;
+ using (var fileStream = new FileStream(path, fileOptions))
{
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
}
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index e4f30d4e0..c8332e44e 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -72,7 +72,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
</ItemGroup>
</Project>
diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 9b130fdfd..ac73cfa42 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -102,7 +102,7 @@ namespace Emby.Drawing
{
var file = await ProcessImage(options).ConfigureAwait(false);
- using (var fileStream = new FileStream(file.Item1, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ using (var fileStream = AsyncFile.OpenRead(file.Item1))
{
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 0308a68e4..3a916e5c0 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -41,20 +41,19 @@ namespace Emby.Server.Implementations.AppBase
xmlSerializer.SerializeToStream(configuration, stream);
// Take the object we just got and serialize it back to bytes
- byte[] newBytes = stream.GetBuffer();
- int newBytesLen = (int)stream.Length;
+ Span<byte> newBytes = stream.GetBuffer().AsSpan(0, (int)stream.Length);
// If the file didn't exist before, or if something has changed, re-save
- if (buffer == null || !newBytes.AsSpan(0, newBytesLen).SequenceEqual(buffer))
+ if (buffer == null || !newBytes.SequenceEqual(buffer))
{
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
Directory.CreateDirectory(directory);
+
// Save it after load in case we got new items
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
- fs.Write(newBytes, 0, newBytesLen);
+ fs.Write(newBytes);
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index dafcded08..c1ce4b557 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -25,13 +25,13 @@
<ItemGroup>
<PackageReference Include="DiscUtils.Udf" Version="0.16.13" />
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.2" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.1.21452.10" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
<PackageReference Include="Mono.Nat" Version="3.0.1" />
- <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.1" />
+ <PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.2" />
<PackageReference Include="sharpcompress" Version="0.30.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index eda494815..711647e7a 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -355,7 +355,12 @@ namespace Emby.Server.Implementations.Library
var query = new InternalItemsQuery(user)
{
IncludeItemTypes = includeItemTypes,
- OrderBy = new[] { (ItemSortBy.DateCreated, SortOrder.Descending) },
+ OrderBy = new[]
+ {
+ (ItemSortBy.DateCreated, SortOrder.Descending),
+ (ItemSortBy.SortName, SortOrder.Descending),
+ (ItemSortBy.ProductionYear, SortOrder.Descending)
+ },
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false,
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 41381d55b..6937cc097 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -46,8 +46,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ using (var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
{
onStarted();
@@ -79,8 +78,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(targetFile) ?? throw new ArgumentException("Path can't be a root directory.", nameof(targetFile)));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var output = new FileStream(targetFile, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
+ await using var output = new FileStream(targetFile, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
onStarted();
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 64e54aa99..980b42729 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -1848,14 +1848,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
Indent = true,
- Encoding = Encoding.UTF8,
- CloseOutput = false
+ Encoding = Encoding.UTF8
};
using (var writer = XmlWriter.Create(stream, settings))
@@ -1913,14 +1911,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
return;
}
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var stream = new FileStream(nfoPath, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (var stream = new FileStream(nfoPath, FileMode.CreateNew, FileAccess.Write, FileShare.None))
{
var settings = new XmlWriterSettings
{
Indent = true,
- Encoding = Encoding.UTF8,
- CloseOutput = false
+ Encoding = Encoding.UTF8
};
var options = _config.GetNfoConfiguration();
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 3060bf270..835028b92 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -94,7 +94,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- _logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ _logFileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
await JsonSerializer.SerializeAsync(_logFileStream, mediaSource, _jsonOptions, cancellationToken).ConfigureAwait(false);
await _logFileStream.WriteAsync(Encoding.UTF8.GetBytes(Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine), cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 8125ed57d..1f963e4a2 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -9,14 +9,15 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
+using System.Net.Http.Headers;
using System.Net.Mime;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
+using Jellyfin.Extensions;
using Jellyfin.Extensions.Json;
-using MediaBrowser.Common;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Cryptography;
@@ -34,7 +35,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private readonly ILogger<SchedulesDirect> _logger;
private readonly IHttpClientFactory _httpClientFactory;
private readonly SemaphoreSlim _tokenSemaphore = new SemaphoreSlim(1, 1);
- private readonly IApplicationHost _appHost;
private readonly ICryptoProvider _cryptoProvider;
private readonly ConcurrentDictionary<string, NameValuePair> _tokens = new ConcurrentDictionary<string, NameValuePair>();
@@ -44,12 +44,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
public SchedulesDirect(
ILogger<SchedulesDirect> logger,
IHttpClientFactory httpClientFactory,
- IApplicationHost appHost,
ICryptoProvider cryptoProvider)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
- _appHost = appHost;
_cryptoProvider = cryptoProvider;
}
@@ -114,18 +112,29 @@ 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<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var dailySchedules = await JsonSerializer.DeserializeAsync<IReadOnlyList<DayDto>>(responseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (dailySchedules == null)
+ {
+ return Array.Empty<ProgramInfo>();
+ }
+
_logger.LogDebug("Found {ScheduleCount} programs on {ChannelID} ScheduleDirect", dailySchedules.Count, channelId);
using var programRequestOptions = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/programs");
programRequestOptions.Headers.TryAddWithoutValidation("token", token);
- var programsID = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
- programRequestOptions.Content = new StringContent("[\"" + string.Join("\", \"", programsID) + "\"]", Encoding.UTF8, MediaTypeNames.Application.Json);
+ var programIds = dailySchedules.SelectMany(d => d.Programs.Select(s => s.ProgramId)).Distinct();
+ programRequestOptions.Content = new ByteArrayContent(JsonSerializer.SerializeToUtf8Bytes(programIds, _jsonOptions));
+ programRequestOptions.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(MediaTypeNames.Application.Json);
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<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var programDetails = await JsonSerializer.DeserializeAsync<IReadOnlyList<ProgramDetailsDto>>(innerResponseStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (programDetails == null)
+ {
+ return Array.Empty<ProgramInfo>();
+ }
+
var programDict = programDetails.ToDictionary(p => p.ProgramId, y => y);
var programIdsWithImages = programDetails
@@ -142,6 +151,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings
// schedule.ProgramId + " which says it has images? " +
// programDict[schedule.ProgramId].hasImageArtwork);
+ if (string.IsNullOrEmpty(schedule.ProgramId))
+ {
+ continue;
+ }
+
if (images != null)
{
var imageIndex = images.FindIndex(i => i.ProgramId == schedule.ProgramId[..10]);
@@ -149,7 +163,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
var programEntry = programDict[schedule.ProgramId];
- var allImages = images[imageIndex].Data ?? new List<ImageDataDto>();
+ var allImages = images[imageIndex].Data;
var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
@@ -217,7 +231,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
private ProgramInfo GetProgram(string channelId, ProgramDto programInfo, ProgramDetailsDto details)
{
- var startAt = GetDate(programInfo.AirDateTime);
+ if (programInfo.AirDateTime == null)
+ {
+ return null;
+ }
+
+ var startAt = programInfo.AirDateTime.Value;
var endAt = startAt.AddSeconds(programInfo.Duration);
var audioType = ProgramAudio.Stereo;
@@ -225,21 +244,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings
string newID = programId + "T" + startAt.Ticks + "C" + channelId;
- if (programInfo.AudioProperties != null)
+ if (programInfo.AudioProperties.Count != 0)
{
- if (programInfo.AudioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
+ if (programInfo.AudioProperties.Contains("atmos", StringComparer.OrdinalIgnoreCase))
{
audioType = ProgramAudio.Atmos;
}
- else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Contains("dd 5.1", StringComparer.OrdinalIgnoreCase))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "dd", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Contains("dd", StringComparer.OrdinalIgnoreCase))
{
audioType = ProgramAudio.DolbyDigital;
}
- else if (programInfo.AudioProperties.Exists(item => string.Equals(item, "stereo", StringComparison.OrdinalIgnoreCase)))
+ else if (programInfo.AudioProperties.Contains("stereo", StringComparer.OrdinalIgnoreCase))
{
audioType = ProgramAudio.Stereo;
}
@@ -355,9 +374,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
}
}
- if (!string.IsNullOrWhiteSpace(details.OriginalAirDate))
+ if (details.OriginalAirDate != null)
{
- info.OriginalAirDate = DateTime.Parse(details.OriginalAirDate, CultureInfo.InvariantCulture);
+ info.OriginalAirDate = details.OriginalAirDate;
info.ProductionYear = info.OriginalAirDate.Value.Year;
}
@@ -384,18 +403,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return info;
}
- private static DateTime GetDate(string value)
- {
- var date = DateTime.ParseExact(value, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'", CultureInfo.InvariantCulture);
-
- if (date.Kind != DateTimeKind.Utc)
- {
- date = DateTime.SpecifyKind(date, DateTimeKind.Utc);
- }
-
- return date;
- }
-
private string GetProgramImage(string apiUrl, IEnumerable<ImageDataDto> images, bool returnDefaultImage, double desiredAspect)
{
var match = images
@@ -449,14 +456,14 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return result;
}
- private async Task<List<ShowImagesDto>> GetImageForPrograms(
+ private async Task<IReadOnlyList<ShowImagesDto>> GetImageForPrograms(
ListingsProviderInfo info,
IReadOnlyList<string> programIds,
CancellationToken cancellationToken)
{
if (programIds.Count == 0)
{
- return new List<ShowImagesDto>();
+ return Array.Empty<ShowImagesDto>();
}
StringBuilder str = new StringBuilder("[", 1 + (programIds.Count * 13));
@@ -480,13 +487,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
using var innerResponse2 = await Send(message, true, info, cancellationToken).ConfigureAwait(false);
await using var response = await innerResponse2.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- return await JsonSerializer.DeserializeAsync<List<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ return await JsonSerializer.DeserializeAsync<IReadOnlyList<ShowImagesDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting image info from schedules direct");
- return new List<ShowImagesDto>();
+ return Array.Empty<ShowImagesDto>();
}
}
@@ -509,7 +516,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var httpResponse = await Send(options, false, info, cancellationToken).ConfigureAwait(false);
await using var response = await httpResponse.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- var root = await JsonSerializer.DeserializeAsync<List<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ var root = await JsonSerializer.DeserializeAsync<IReadOnlyList<HeadendsDto>>(response, _jsonOptions, cancellationToken).ConfigureAwait(false);
if (root != null)
{
@@ -520,7 +527,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
lineups.Add(new NameIdPair
{
Name = string.IsNullOrWhiteSpace(lineup.Name) ? lineup.Lineup : lineup.Name,
- Id = lineup.Uri[18..]
+ Id = lineup.Uri?[18..]
});
}
}
@@ -651,7 +658,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings
response.EnsureSuccessStatusCode();
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
var root = await JsonSerializer.DeserializeAsync<TokenDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- if (string.Equals(root.Message, "OK", StringComparison.Ordinal))
+ if (string.Equals(root?.Message, "OK", StringComparison.Ordinal))
{
_logger.LogInformation("Authenticated with Schedules Direct token: {Token}", root.Token);
return root.Token;
@@ -708,12 +715,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
using var response = httpResponse.Content;
var root = await JsonSerializer.DeserializeAsync<LineupsDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- return root.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase));
+ return root?.Lineups.Any(i => string.Equals(info.ListingsId, i.Lineup, StringComparison.OrdinalIgnoreCase)) ?? false;
}
catch (HttpRequestException ex)
{
// SchedulesDirect returns 400 if no lineups are configured.
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.BadRequest)
+ if (ex.StatusCode is HttpStatusCode.BadRequest)
{
return false;
}
@@ -779,10 +786,15 @@ 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<ChannelDto>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false);
+ if (root == null)
+ {
+ return new List<ChannelInfo>();
+ }
+
_logger.LogInformation("Found {ChannelCount} channels on the lineup on ScheduleDirect", root.Map.Count);
_logger.LogInformation("Mapping Stations to Channel");
- var allStations = root.Stations ?? new List<StationDto>();
+ var allStations = root.Stations;
var map = root.Map;
var list = new List<ChannelInfo>(map.Count);
@@ -790,11 +802,10 @@ namespace Emby.Server.Implementations.LiveTv.Listings
{
var channelNumber = GetChannelNumber(channel);
- var station = allStations.Find(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase))
- ?? new StationDto
- {
- StationId = channel.StationId
- };
+ var stationIndex = allStations.FindIndex(item => string.Equals(item.StationId, channel.StationId, StringComparison.OrdinalIgnoreCase));
+ var station = stationIndex == -1
+ ? new StationDto { StationId = channel.StationId }
+ : allStations[stationIndex];
var channelInfo = new ChannelInfo
{
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
index b881b307c..95ac996e0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/BroadcasterDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,24 +11,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the city.
/// </summary>
[JsonPropertyName("city")]
- public string City { get; set; }
+ public string? City { get; set; }
/// <summary>
/// Gets or sets the state.
/// </summary>
[JsonPropertyName("state")]
- public string State { get; set; }
+ public string? State { get; set; }
/// <summary>
/// Gets or sets the postal code.
/// </summary>
[JsonPropertyName("postalCode")]
- public string Postalcode { get; set; }
+ public string? Postalcode { get; set; }
/// <summary>
/// Gets or sets the country.
/// </summary>
[JsonPropertyName("country")]
- public string Country { get; set; }
+ public string? Country { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
index 96b67d1eb..f6251b9ad 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CaptionDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the content.
/// </summary>
[JsonPropertyName("content")]
- public string Content { get; set; }
+ public string? Content { get; set; }
/// <summary>
/// Gets or sets the lang.
/// </summary>
[JsonPropertyName("lang")]
- public string Lang { get; set; }
+ public string? Lang { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
index dac6f5f3e..0b7a2c63a 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CastDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,36 +11,36 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the billing order.
/// </summary>
[JsonPropertyName("billingOrder")]
- public string BillingOrder { get; set; }
+ public string? BillingOrder { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
[JsonPropertyName("role")]
- public string Role { get; set; }
+ public string? Role { get; set; }
/// <summary>
/// Gets or sets the name id.
/// </summary>
[JsonPropertyName("nameId")]
- public string NameId { get; set; }
+ public string? NameId { get; set; }
/// <summary>
/// Gets or sets the person id.
/// </summary>
[JsonPropertyName("personId")]
- public string PersonId { get; set; }
+ public string? PersonId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the character name.
/// </summary>
[JsonPropertyName("characterName")]
- public string CharacterName { get; set; }
+ public string? CharacterName { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
index 8c9c2c1fc..87c327ed8 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ChannelDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,18 +13,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the list of maps.
/// </summary>
[JsonPropertyName("map")]
- public List<MapDto> Map { get; set; }
+ public IReadOnlyList<MapDto> Map { get; set; } = Array.Empty<MapDto>();
/// <summary>
/// Gets or sets the list of stations.
/// </summary>
[JsonPropertyName("stations")]
- public List<StationDto> Stations { get; set; }
+ public IReadOnlyList<StationDto> Stations { get; set; } = Array.Empty<StationDto>();
/// <summary>
/// Gets or sets the metadata.
/// </summary>
[JsonPropertyName("metadata")]
- public MetadataDto Metadata { get; set; }
+ public MetadataDto? Metadata { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
index 135b5bb08..c19cd2e48 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ContentRatingDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the body.
/// </summary>
[JsonPropertyName("body")]
- public string Body { get; set; }
+ public string? Body { get; set; }
/// <summary>
/// Gets or sets the code.
/// </summary>
[JsonPropertyName("code")]
- public string Code { get; set; }
+ public string? Code { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
index 82d1001c8..f00c9accd 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/CrewDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,30 +11,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the billing order.
/// </summary>
[JsonPropertyName("billingOrder")]
- public string BillingOrder { get; set; }
+ public string? BillingOrder { get; set; }
/// <summary>
/// Gets or sets the role.
/// </summary>
[JsonPropertyName("role")]
- public string Role { get; set; }
+ public string? Role { get; set; }
/// <summary>
/// Gets or sets the name id.
/// </summary>
[JsonPropertyName("nameId")]
- public string NameId { get; set; }
+ public string? NameId { get; set; }
/// <summary>
/// Gets or sets the person id.
/// </summary>
[JsonPropertyName("personId")]
- public string PersonId { get; set; }
+ public string? PersonId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
- public string Name { get; set; }
+ public string? Name { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
index 68876b068..1a371965c 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DayDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -11,29 +10,21 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
public class DayDto
{
/// <summary>
- /// Initializes a new instance of the <see cref="DayDto"/> class.
- /// </summary>
- public DayDto()
- {
- Programs = new List<ProgramDto>();
- }
-
- /// <summary>
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
- public string StationId { get; set; }
+ public string? StationId { get; set; }
/// <summary>
/// Gets or sets the list of programs.
/// </summary>
[JsonPropertyName("programs")]
- public List<ProgramDto> Programs { get; set; }
+ public IReadOnlyList<ProgramDto> Programs { get; set; } = Array.Empty<ProgramDto>();
/// <summary>
/// Gets or sets the metadata schedule.
/// </summary>
[JsonPropertyName("metadata")]
- public MetadataScheduleDto Metadata { get; set; }
+ public MetadataScheduleDto? Metadata { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
index d3e6ff393..ca6ae7fb1 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description1000Dto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
- public string DescriptionLanguage { get; set; }
+ public string? DescriptionLanguage { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonPropertyName("description")]
- public string Description { get; set; }
+ public string? Description { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
index 04360266c..1577219ed 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/Description100Dto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
- public string DescriptionLanguage { get; set; }
+ public string? DescriptionLanguage { get; set; }
/// <summary>
/// Gets or sets the description.
/// </summary>
[JsonPropertyName("description")]
- public string Description { get; set; }
+ public string? Description { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
index 3af36ae96..eaf4a340b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/DescriptionsProgramDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the list of description 100.
/// </summary>
[JsonPropertyName("description100")]
- public List<Description100Dto> Description100 { get; set; }
+ public IReadOnlyList<Description100Dto> Description100 { get; set; } = Array.Empty<Description100Dto>();
/// <summary>
/// Gets or sets the list of description1000.
/// </summary>
[JsonPropertyName("description1000")]
- public List<Description1000Dto> Description1000 { get; set; }
+ public IReadOnlyList<Description1000Dto> Description1000 { get; set; } = Array.Empty<Description1000Dto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
index c3b2bd9c1..fbdfb1f71 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/EventDetailsDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,6 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the sub type.
/// </summary>
[JsonPropertyName("subType")]
- public string SubType { get; set; }
+ public string? SubType { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
index 3d8bea362..6852d89d7 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/GracenoteDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
index 1fb3decb2..b9844562f 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/HeadendsDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,24 +13,24 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the headend.
/// </summary>
[JsonPropertyName("headend")]
- public string Headend { get; set; }
+ public string? Headend { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
- public string Transport { get; set; }
+ public string? Transport { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
[JsonPropertyName("location")]
- public string Location { get; set; }
+ public string? Location { get; set; }
/// <summary>
/// Gets or sets the list of lineups.
/// </summary>
[JsonPropertyName("lineups")]
- public List<LineupDto> Lineups { get; set; }
+ public IReadOnlyList<LineupDto> Lineups { get; set; } = Array.Empty<LineupDto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
index 4e9efc60f..a1ae3ca6d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ImageDataDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,60 +11,60 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the width.
/// </summary>
[JsonPropertyName("width")]
- public string Width { get; set; }
+ public string? Width { get; set; }
/// <summary>
/// Gets or sets the height.
/// </summary>
[JsonPropertyName("height")]
- public string Height { get; set; }
+ public string? Height { get; set; }
/// <summary>
/// Gets or sets the uri.
/// </summary>
[JsonPropertyName("uri")]
- public string Uri { get; set; }
+ public string? Uri { get; set; }
/// <summary>
/// Gets or sets the size.
/// </summary>
[JsonPropertyName("size")]
- public string Size { get; set; }
+ public string? Size { get; set; }
/// <summary>
/// Gets or sets the aspect.
/// </summary>
[JsonPropertyName("aspect")]
- public string Aspect { get; set; }
+ public string? Aspect { get; set; }
/// <summary>
/// Gets or sets the category.
/// </summary>
[JsonPropertyName("category")]
- public string Category { get; set; }
+ public string? Category { get; set; }
/// <summary>
/// Gets or sets the text.
/// </summary>
[JsonPropertyName("text")]
- public string Text { get; set; }
+ public string? Text { get; set; }
/// <summary>
/// Gets or sets the primary.
/// </summary>
[JsonPropertyName("primary")]
- public string Primary { get; set; }
+ public string? Primary { get; set; }
/// <summary>
/// Gets or sets the tier.
/// </summary>
[JsonPropertyName("tier")]
- public string Tier { get; set; }
+ public string? Tier { get; set; }
/// <summary>
/// Gets or sets the caption.
/// </summary>
[JsonPropertyName("caption")]
- public CaptionDto Caption { get; set; }
+ public CaptionDto? Caption { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
index 52e920aa6..3dc64e5d8 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,30 +11,36 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the linup.
/// </summary>
[JsonPropertyName("lineup")]
- public string Lineup { get; set; }
+ public string? Lineup { get; set; }
/// <summary>
/// Gets or sets the lineup name.
/// </summary>
[JsonPropertyName("name")]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
- public string Transport { get; set; }
+ public string? Transport { get; set; }
/// <summary>
/// Gets or sets the location.
/// </summary>
[JsonPropertyName("location")]
- public string Location { get; set; }
+ public string? Location { get; set; }
/// <summary>
/// Gets or sets the uri.
/// </summary>
[JsonPropertyName("uri")]
- public string Uri { get; set; }
+ public string? Uri { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this lineup was deleted.
+ /// </summary>
+ [JsonPropertyName("isDeleted")]
+ public bool? IsDeleted { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
index 15139ba3b..f19081781 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LineupsDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -20,18 +19,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the server id.
/// </summary>
[JsonPropertyName("serverID")]
- public string ServerId { get; set; }
+ public string? ServerId { get; set; }
/// <summary>
/// Gets or sets the datetime.
/// </summary>
[JsonPropertyName("datetime")]
- public string Datetime { get; set; }
+ public DateTime? LineupTimestamp { get; set; }
/// <summary>
/// Gets or sets the list of lineups.
/// </summary>
[JsonPropertyName("lineups")]
- public List<LineupDto> Lineups { get; set; }
+ public IReadOnlyList<LineupDto> Lineups { get; set; } = Array.Empty<LineupDto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
index 7b235ed7f..fecc55e03 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/LogoDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,7 +11,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the url.
/// </summary>
[JsonPropertyName("URL")]
- public string Url { get; set; }
+ public string? Url { get; set; }
/// <summary>
/// Gets or sets the height.
@@ -31,6 +29,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
- public string Md5 { get; set; }
+ public string? Md5 { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
index 5140277b2..ffd02d474 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MapDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,19 +11,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
- public string StationId { get; set; }
+ public string? StationId { get; set; }
/// <summary>
/// Gets or sets the channel.
/// </summary>
[JsonPropertyName("channel")]
- public string Channel { get; set; }
+ public string? Channel { get; set; }
+
+ /// <summary>
+ /// Gets or sets the provider callsign.
+ /// </summary>
+ [JsonPropertyName("providerCallsign")]
+ public string? ProvderCallsign { get; set; }
/// <summary>
/// Gets or sets the logical channel number.
/// </summary>
[JsonPropertyName("logicalChannelNumber")]
- public string LogicalChannelNumber { get; set; }
+ public string? LogicalChannelNumber { get; set; }
/// <summary>
/// Gets or sets the uhfvhf.
@@ -44,5 +48,11 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// </summary>
[JsonPropertyName("atscMinor")]
public int AtscMinor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the match type.
+ /// </summary>
+ [JsonPropertyName("matchType")]
+ public string? MatchType { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
index 5a3893a35..40faa493c 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,18 +11,18 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the linup.
/// </summary>
[JsonPropertyName("lineup")]
- public string Lineup { get; set; }
+ public string? Lineup { get; set; }
/// <summary>
/// Gets or sets the modified timestamp.
/// </summary>
[JsonPropertyName("modified")]
- public string Modified { get; set; }
+ public string? Modified { get; set; }
/// <summary>
/// Gets or sets the transport.
/// </summary>
[JsonPropertyName("transport")]
- public string Transport { get; set; }
+ public string? Transport { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
index 4057e9802..43f290156 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataProgramsDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -12,7 +10,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// <summary>
/// Gets or sets the gracenote object.
/// </summary>
- [JsonPropertyName("gracenote")]
- public GracenoteDto Gracenote { get; set; }
+ [JsonPropertyName("Gracenote")]
+ public GracenoteDto? Gracenote { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
index 4979296da..04560ab55 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MetadataScheduleDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,25 +12,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the modified timestamp.
/// </summary>
[JsonPropertyName("modified")]
- public string Modified { get; set; }
+ public string? Modified { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
- public string Md5 { get; set; }
+ public string? Md5 { get; set; }
/// <summary>
/// Gets or sets the start date.
/// </summary>
[JsonPropertyName("startDate")]
- public string StartDate { get; set; }
+ public DateTime? StartDate { get; set; }
/// <summary>
/// Gets or sets the end date.
/// </summary>
[JsonPropertyName("endDate")]
- public string EndDate { get; set; }
+ public DateTime? EndDate { get; set; }
/// <summary>
/// Gets or sets the days count.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
index 48d731d89..31bef423b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MovieDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,7 +13,7 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the year.
/// </summary>
[JsonPropertyName("year")]
- public string Year { get; set; }
+ public string? Year { get; set; }
/// <summary>
/// Gets or sets the duration.
@@ -26,6 +25,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the list of quality rating.
/// </summary>
[JsonPropertyName("qualityRating")]
- public List<QualityRatingDto> QualityRating { get; set; }
+ public IReadOnlyList<QualityRatingDto> QualityRating { get; set; } = Array.Empty<QualityRatingDto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
index 42eddfff2..e8b15dc07 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/MultipartDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
index a84c47c12..84c48f67f 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDetailsDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,85 +13,85 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the audience.
/// </summary>
[JsonPropertyName("audience")]
- public string Audience { get; set; }
+ public string? Audience { get; set; }
/// <summary>
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
- public string ProgramId { get; set; }
+ public string? ProgramId { get; set; }
/// <summary>
/// Gets or sets the list of titles.
/// </summary>
[JsonPropertyName("titles")]
- public List<TitleDto> Titles { get; set; }
+ public IReadOnlyList<TitleDto> Titles { get; set; } = Array.Empty<TitleDto>();
/// <summary>
/// Gets or sets the event details object.
/// </summary>
[JsonPropertyName("eventDetails")]
- public EventDetailsDto EventDetails { get; set; }
+ public EventDetailsDto? EventDetails { get; set; }
/// <summary>
/// Gets or sets the descriptions.
/// </summary>
[JsonPropertyName("descriptions")]
- public DescriptionsProgramDto Descriptions { get; set; }
+ public DescriptionsProgramDto? Descriptions { get; set; }
/// <summary>
/// Gets or sets the original air date.
/// </summary>
[JsonPropertyName("originalAirDate")]
- public string OriginalAirDate { get; set; }
+ public DateTime? OriginalAirDate { get; set; }
/// <summary>
/// Gets or sets the list of genres.
/// </summary>
[JsonPropertyName("genres")]
- public List<string> Genres { get; set; }
+ public IReadOnlyList<string> Genres { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the episode title.
/// </summary>
[JsonPropertyName("episodeTitle150")]
- public string EpisodeTitle150 { get; set; }
+ public string? EpisodeTitle150 { get; set; }
/// <summary>
/// Gets or sets the list of metadata.
/// </summary>
[JsonPropertyName("metadata")]
- public List<MetadataProgramsDto> Metadata { get; set; }
+ public IReadOnlyList<MetadataProgramsDto> Metadata { get; set; } = Array.Empty<MetadataProgramsDto>();
/// <summary>
/// Gets or sets the list of content raitings.
/// </summary>
[JsonPropertyName("contentRating")]
- public List<ContentRatingDto> ContentRating { get; set; }
+ public IReadOnlyList<ContentRatingDto> ContentRating { get; set; } = Array.Empty<ContentRatingDto>();
/// <summary>
/// Gets or sets the list of cast.
/// </summary>
[JsonPropertyName("cast")]
- public List<CastDto> Cast { get; set; }
+ public IReadOnlyList<CastDto> Cast { get; set; } = Array.Empty<CastDto>();
/// <summary>
/// Gets or sets the list of crew.
/// </summary>
[JsonPropertyName("crew")]
- public List<CrewDto> Crew { get; set; }
+ public IReadOnlyList<CrewDto> Crew { get; set; } = Array.Empty<CrewDto>();
/// <summary>
/// Gets or sets the entity type.
/// </summary>
[JsonPropertyName("entityType")]
- public string EntityType { get; set; }
+ public string? EntityType { get; set; }
/// <summary>
/// Gets or sets the show type.
/// </summary>
[JsonPropertyName("showType")]
- public string ShowType { get; set; }
+ public string? ShowType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether there is image artwork.
@@ -104,54 +103,54 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the primary image.
/// </summary>
[JsonPropertyName("primaryImage")]
- public string PrimaryImage { get; set; }
+ public string? PrimaryImage { get; set; }
/// <summary>
/// Gets or sets the thumb image.
/// </summary>
[JsonPropertyName("thumbImage")]
- public string ThumbImage { get; set; }
+ public string? ThumbImage { get; set; }
/// <summary>
/// Gets or sets the backdrop image.
/// </summary>
[JsonPropertyName("backdropImage")]
- public string BackdropImage { get; set; }
+ public string? BackdropImage { get; set; }
/// <summary>
/// Gets or sets the banner image.
/// </summary>
[JsonPropertyName("bannerImage")]
- public string BannerImage { get; set; }
+ public string? BannerImage { get; set; }
/// <summary>
/// Gets or sets the image id.
/// </summary>
[JsonPropertyName("imageID")]
- public string ImageId { get; set; }
+ public string? ImageId { get; set; }
/// <summary>
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
- public string Md5 { get; set; }
+ public string? Md5 { get; set; }
/// <summary>
/// Gets or sets the list of content advisory.
/// </summary>
[JsonPropertyName("contentAdvisory")]
- public List<string> ContentAdvisory { get; set; }
+ public IReadOnlyList<string> ContentAdvisory { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the movie object.
/// </summary>
[JsonPropertyName("movie")]
- public MovieDto Movie { get; set; }
+ public MovieDto? Movie { get; set; }
/// <summary>
/// Gets or sets the list of recommendations.
/// </summary>
[JsonPropertyName("recommendations")]
- public List<RecommendationDto> Recommendations { get; set; }
+ public IReadOnlyList<RecommendationDto> Recommendations { get; set; } = Array.Empty<RecommendationDto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
index ad5389100..60389b45b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ProgramDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,13 +13,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
- public string ProgramId { get; set; }
+ public string? ProgramId { get; set; }
/// <summary>
/// Gets or sets the air date time.
/// </summary>
[JsonPropertyName("airDateTime")]
- public string AirDateTime { get; set; }
+ public DateTime? AirDateTime { get; set; }
/// <summary>
/// Gets or sets the duration.
@@ -32,25 +31,25 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the md5.
/// </summary>
[JsonPropertyName("md5")]
- public string Md5 { get; set; }
+ public string? Md5 { get; set; }
/// <summary>
/// Gets or sets the list of audio properties.
/// </summary>
[JsonPropertyName("audioProperties")]
- public List<string> AudioProperties { get; set; }
+ public IReadOnlyList<string> AudioProperties { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the list of video properties.
/// </summary>
[JsonPropertyName("videoProperties")]
- public List<string> VideoProperties { get; set; }
+ public IReadOnlyList<string> VideoProperties { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the list of ratings.
/// </summary>
[JsonPropertyName("ratings")]
- public List<RatingDto> Ratings { get; set; }
+ public IReadOnlyList<RatingDto> Ratings { get; set; } = Array.Empty<RatingDto>();
/// <summary>
/// Gets or sets a value indicating whether this program is new.
@@ -62,13 +61,13 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the multipart object.
/// </summary>
[JsonPropertyName("multipart")]
- public MultipartDto Multipart { get; set; }
+ public MultipartDto? Multipart { get; set; }
/// <summary>
/// Gets or sets the live tape delay.
/// </summary>
[JsonPropertyName("liveTapeDelay")]
- public string LiveTapeDelay { get; set; }
+ public string? LiveTapeDelay { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this is the premiere.
@@ -86,6 +85,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the premiere or finale.
/// </summary>
[JsonPropertyName("isPremiereOrFinale")]
- public string IsPremiereOrFinale { get; set; }
+ public string? IsPremiereOrFinale { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
index 5cd0a7459..c5ddcf7c5 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/QualityRatingDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,30 +11,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the ratings body.
/// </summary>
[JsonPropertyName("ratingsBody")]
- public string RatingsBody { get; set; }
+ public string? RatingsBody { get; set; }
/// <summary>
/// Gets or sets the rating.
/// </summary>
[JsonPropertyName("rating")]
- public string Rating { get; set; }
+ public string? Rating { get; set; }
/// <summary>
/// Gets or sets the min rating.
/// </summary>
[JsonPropertyName("minRating")]
- public string MinRating { get; set; }
+ public string? MinRating { get; set; }
/// <summary>
/// Gets or sets the max rating.
/// </summary>
[JsonPropertyName("maxRating")]
- public string MaxRating { get; set; }
+ public string? MaxRating { get; set; }
/// <summary>
/// Gets or sets the increment.
/// </summary>
[JsonPropertyName("increment")]
- public string Increment { get; set; }
+ public string? Increment { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
index 948b83144..e04b619a4 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RatingDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the body.
/// </summary>
[JsonPropertyName("body")]
- public string Body { get; set; }
+ public string? Body { get; set; }
/// <summary>
/// Gets or sets the code.
/// </summary>
[JsonPropertyName("code")]
- public string Code { get; set; }
+ public string? Code { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
index 1308f45ce..c8f79fd1c 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RecommendationDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,12 +11,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
- public string ProgramId { get; set; }
+ public string? ProgramId { get; set; }
/// <summary>
/// Gets or sets the title.
/// </summary>
[JsonPropertyName("title120")]
- public string Title120 { get; set; }
+ public string? Title120 { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
index fb7a31ac8..0cd05709b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/RequestScheduleForChannelDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
- public string StationId { get; set; }
+ public string? StationId { get; set; }
/// <summary>
/// Gets or sets the list of dates.
/// </summary>
[JsonPropertyName("date")]
- public List<string> Date { get; set; }
+ public IReadOnlyList<string> Date { get; set; } = Array.Empty<string>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
index 34302370d..84e224b71 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/ShowImagesDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,12 +13,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the program id.
/// </summary>
[JsonPropertyName("programID")]
- public string ProgramId { get; set; }
+ public string? ProgramId { get; set; }
/// <summary>
/// Gets or sets the list of data.
/// </summary>
[JsonPropertyName("data")]
- public List<ImageDataDto> Data { get; set; }
+ public IReadOnlyList<ImageDataDto> Data { get; set; } = Array.Empty<ImageDataDto>();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
index c37f19678..d797fd49b 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/StationDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
@@ -14,49 +13,49 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the station id.
/// </summary>
[JsonPropertyName("stationID")]
- public string StationId { get; set; }
+ public string? StationId { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
[JsonPropertyName("name")]
- public string Name { get; set; }
+ public string? Name { get; set; }
/// <summary>
/// Gets or sets the callsign.
/// </summary>
[JsonPropertyName("callsign")]
- public string Callsign { get; set; }
+ public string? Callsign { get; set; }
/// <summary>
/// Gets or sets the broadcast language.
/// </summary>
[JsonPropertyName("broadcastLanguage")]
- public List<string> BroadcastLanguage { get; set; }
+ public IReadOnlyList<string> BroadcastLanguage { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the description language.
/// </summary>
[JsonPropertyName("descriptionLanguage")]
- public List<string> DescriptionLanguage { get; set; }
+ public IReadOnlyList<string> DescriptionLanguage { get; set; } = Array.Empty<string>();
/// <summary>
/// Gets or sets the broadcaster.
/// </summary>
[JsonPropertyName("broadcaster")]
- public BroadcasterDto Broadcaster { get; set; }
+ public BroadcasterDto? Broadcaster { get; set; }
/// <summary>
/// Gets or sets the affiliate.
/// </summary>
[JsonPropertyName("affiliate")]
- public string Affiliate { get; set; }
+ public string? Affiliate { get; set; }
/// <summary>
/// Gets or sets the logo.
/// </summary>
[JsonPropertyName("logo")]
- public LogoDto Logo { get; set; }
+ public LogoDto? Logo { get; set; }
/// <summary>
/// Gets or sets a value indicating whether it is commercial free.
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
index 06c95524b..61cd4a9b0 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TitleDto.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -13,6 +11,6 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the title.
/// </summary>
[JsonPropertyName("title120")]
- public string Title120 { get; set; }
+ public string? Title120 { get; set; }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
index c3ec1c7d6..afb999486 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirectDtos/TokenDto.cs
@@ -1,5 +1,4 @@
-#nullable disable
-
+using System;
using System.Text.Json.Serialization;
namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
@@ -19,18 +18,30 @@ namespace Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos
/// Gets or sets the response message.
/// </summary>
[JsonPropertyName("message")]
- public string Message { get; set; }
+ public string? Message { get; set; }
/// <summary>
/// Gets or sets the server id.
/// </summary>
[JsonPropertyName("serverID")]
- public string ServerId { get; set; }
+ public string? ServerId { get; set; }
/// <summary>
/// Gets or sets the token.
/// </summary>
[JsonPropertyName("token")]
- public string Token { get; set; }
+ public string? Token { get; set; }
+
+ /// <summary>
+ /// Gets or sets the current datetime.
+ /// </summary>
+ [JsonPropertyName("datetime")]
+ public DateTime? TokenTimestamp { get; set; }
+
+ /// <summary>
+ /// Gets or sets the response message.
+ /// </summary>
+ [JsonPropertyName("response")]
+ public string? Response { get; set; }
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/ar.json b/Emby.Server.Implementations/Localization/Core/ar.json
index 5a4a4d5a9..ee70aa8d8 100644
--- a/Emby.Server.Implementations/Localization/Core/ar.json
+++ b/Emby.Server.Implementations/Localization/Core/ar.json
@@ -15,7 +15,7 @@
"Favorites": "المفضلة",
"Folders": "المجلدات",
"Genres": "التضنيفات",
- "HeaderAlbumArtists": "فناني الألبومات",
+ "HeaderAlbumArtists": "ألبوم الفنان",
"HeaderContinueWatching": "استئناف",
"HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "التلفاز المباشر",
"HeaderNextUp": "التالي",
"HeaderRecordingGroups": "مجموعات التسجيل",
- "HomeVideos": "الفيديوهات المنزلية",
+ "HomeVideos": "الفيديوهات الشخصية",
"Inherit": "توريث",
"ItemAddedWithName": "تم إضافة {0} للمكتبة",
"ItemRemovedWithName": "تم إزالة {0} من المكتبة",
diff --git a/Emby.Server.Implementations/Localization/Core/cy.json b/Emby.Server.Implementations/Localization/Core/cy.json
new file mode 100644
index 000000000..0fa72dea4
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/cy.json
@@ -0,0 +1,14 @@
+{
+ "DeviceOnlineWithName": "Mae {0} wedi'i gysylltu",
+ "DeviceOfflineWithName": "Mae {0} wedi datgysylltu",
+ "Default": "Diofyn",
+ "Collections": "Casgliadau",
+ "ChapterNameValue": "Pennod {0}",
+ "Channels": "Sianeli",
+ "CameraImageUploadedFrom": "Mae delwedd camera newydd wedi'i lanlwytho o {0}",
+ "Books": "Llyfrau",
+ "AuthenticationSucceededWithUserName": "{0} wedi’i ddilysu’n llwyddiannus",
+ "Artists": "Artistiaid",
+ "AppDeviceValues": "Ap: {0}, Dyfais: {1}",
+ "Albums": "Albwmau"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/en-GB.json b/Emby.Server.Implementations/Localization/Core/en-GB.json
index 8b2e8b6b1..86ce9240e 100644
--- a/Emby.Server.Implementations/Localization/Core/en-GB.json
+++ b/Emby.Server.Implementations/Localization/Core/en-GB.json
@@ -15,7 +15,7 @@
"Favorites": "Favourites",
"Folders": "Folders",
"Genres": "Genres",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Artist's Album",
"HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favourite Albums",
"HeaderFavoriteArtists": "Favourite Artists",
@@ -25,7 +25,7 @@
"HeaderLiveTV": "Live TV",
"HeaderNextUp": "Next Up",
"HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
+ "HomeVideos": "Home Videos",
"Inherit": "Inherit",
"ItemAddedWithName": "{0} was added to the library",
"ItemRemovedWithName": "{0} was removed from the library",
@@ -39,7 +39,7 @@
"MixedContent": "Mixed content",
"Movies": "Movies",
"Music": "Music",
- "MusicVideos": "Music videos",
+ "MusicVideos": "Music Videos",
"NameInstallFailed": "{0} installation failed",
"NameSeasonNumber": "Season {0}",
"NameSeasonUnknown": "Season Unknown",
diff --git a/Emby.Server.Implementations/Localization/Core/eo.json b/Emby.Server.Implementations/Localization/Core/eo.json
index ca615cc8c..7ce87502c 100644
--- a/Emby.Server.Implementations/Localization/Core/eo.json
+++ b/Emby.Server.Implementations/Localization/Core/eo.json
@@ -1,17 +1,17 @@
{
"NotificationOptionInstallationFailed": "Instalada fiasko",
- "NotificationOptionAudioPlaybackStopped": "Sono de ludado haltis",
- "NotificationOptionAudioPlayback": "Ludado de sono startis",
+ "NotificationOptionAudioPlaybackStopped": "Ludado de sono haltis",
+ "NotificationOptionAudioPlayback": "Ludado de sono lanĉis",
"NameSeasonUnknown": "Sezono Nekonata",
"NameSeasonNumber": "Sezono {0}",
"NameInstallFailed": "{0} instalado fiaskis",
"Music": "Muziko",
"Movies": "Filmoj",
- "ItemRemovedWithName": "{0} forigis el la biblioteko",
- "ItemAddedWithName": "{0} aldonis al la biblioteko",
- "HeaderLiveTV": "Viva Televido",
- "HeaderContinueWatching": "Daŭrigi Spektado",
- "HeaderAlbumArtists": "Artistoj de Albumo",
+ "ItemRemovedWithName": "{0} forigis el la libraro",
+ "ItemAddedWithName": "{0} aldonis al la libraro",
+ "HeaderLiveTV": "TV-etero",
+ "HeaderContinueWatching": "Daŭrigi Spektadon",
+ "HeaderAlbumArtists": "Albumo de artisto",
"Folders": "Dosierujoj",
"DeviceOnlineWithName": "{0} estas konektita",
"Default": "Defaŭlte",
@@ -30,7 +30,7 @@
"User": "Uzanto",
"System": "Sistemo",
"Songs": "Kantoj",
- "ScheduledTaskStartedWithName": "{0} komencis",
+ "ScheduledTaskStartedWithName": "{0} lanĉis",
"ScheduledTaskFailedWithName": "{0} malsukcesis",
"PluginUninstalledWithName": "{0} malinstaliĝis",
"PluginInstalledWithName": "{0} instaliĝis",
@@ -43,5 +43,81 @@
"MusicVideos": "Muzikvideoj",
"LabelIpAddressValue": "IP-adreso: {0}",
"Genres": "Ĝenroj",
- "DeviceOfflineWithName": "{0} malkonektis"
+ "DeviceOfflineWithName": "{0} malkonektis",
+ "HeaderFavoriteArtists": "Favorataj Artistoj",
+ "Shows": "Serioj",
+ "HeaderFavoriteShows": "Favorataj Serioj",
+ "TvShows": "TV-serioj",
+ "Favorites": "Favoratoj",
+ "TaskCleanLogs": "Purigi Ĵurnalan Katalogon",
+ "TaskRefreshLibrary": "Skanu Plurmedian Libraron",
+ "ValueSpecialEpisodeName": "Speciala - {0}",
+ "TaskOptimizeDatabase": "Optimigi datumbazon",
+ "TaskRefreshChannels": "Refreŝigi Kanalojn",
+ "TaskUpdatePlugins": "Ĝisdatigi Kromprogramojn",
+ "TaskRefreshPeople": "Refreŝigi Homojn",
+ "TasksChannelsCategory": "Interretaj Kanaloj",
+ "ProviderValue": "Provizanto: {0}",
+ "NotificationOptionPluginError": "Kromprograma malsukceso",
+ "MixedContent": "Miksita enhavo",
+ "TasksApplicationCategory": "Aplikaĵo",
+ "TasksMaintenanceCategory": "Prizorgado",
+ "Undefined": "Nedifinita",
+ "Sync": "Sinkronigo",
+ "Latest": "Plej novaj",
+ "Inherit": "Hereda",
+ "HomeVideos": "Hejmaj Videoj",
+ "HeaderNextUp": "Sekva Plue",
+ "HeaderFavoriteSongs": "Favorataj Kantoj",
+ "HeaderFavoriteEpisodes": "Favorataj Epizodoj",
+ "HeaderFavoriteAlbums": "Favorataj Albumoj",
+ "Forced": "Forcita",
+ "ServerNameNeedsToBeRestarted": "{0} devas esti relanĉita",
+ "NotificationOptionVideoPlayback": "La videoludado lanĉis",
+ "NotificationOptionServerRestartRequired": "Servila relanĉigo bezonata",
+ "TaskOptimizeDatabaseDescription": "Kompaktigas datenbazon kaj trunkas liberan lokon. Lanĉi ĉi tiun taskon post la librara skanado aŭ fari aliajn ŝanĝojn, kiuj implicas datenbazajn modifojn, povus plibonigi rendimenton.",
+ "TaskUpdatePluginsDescription": "Elŝutas kaj instalas ĝisdatigojn por kromprogramojn, kiuj estas agorditaj por ĝisdatigi aŭtomate.",
+ "TaskDownloadMissingSubtitlesDescription": "Serĉas en interreto mankantajn subtekstojn surbaze de metadatena agordaro.",
+ "TaskRefreshPeopleDescription": "Ĝisdatigas metadatenojn por aktoroj kaj reĵisoroj en via plurmedia libraro.",
+ "TaskCleanLogsDescription": "Forigas ĵurnalajn dosierojn aĝajn pli ol {0} tagojn.",
+ "TaskRefreshLibraryDescription": "Skanas vian plurmedian libraron por novaj dosieroj kaj refreŝigas metadatenaron.",
+ "NewVersionIsAvailable": "Nova versio de Jellyfin Server estas elŝutebla.",
+ "TaskCleanCacheDescription": "Forigas stapla dosierojn ne plu necesajn de la sistemo.",
+ "TaskCleanActivityLogDescription": "Forigas aktivecan ĵurnalaĵojn pli malnovajn ol la agordita aĝo.",
+ "TaskCleanTranscodeDescription": "Forigas transkodajn dosierojn aĝajn pli ol unu tagon.",
+ "ValueHasBeenAddedToLibrary": "{0} estis aldonita al via plurmedia libraro",
+ "SubtitleDownloadFailureFromForItem": "Subtekstoj malsukcesis elŝuti de {0} por {1}",
+ "StartupEmbyServerIsLoading": "Jellyfin Server ŝarĝas. Provi denove baldaŭ.",
+ "TaskRefreshChapterImagesDescription": "Kreas bildetojn por videoj kiuj havas ĉapitrojn.",
+ "UserStoppedPlayingItemWithValues": "{0} finis ludi {1} ĉe {2}",
+ "UserPolicyUpdatedWithName": "Uzanta politiko estis ĝisdatigita por {0}",
+ "UserPasswordChangedWithName": "Pasvorto estis ŝanĝita por uzanto {0}",
+ "UserStartedPlayingItemWithValues": "{0} ludas {1} ĉe {2}",
+ "UserLockedOutWithName": "Uzanto {0} estas elŝlosita",
+ "UserOnlineFromDevice": "{0} estas enreta de {1}",
+ "UserOfflineFromDevice": "{0} malkonektis de {1}",
+ "UserDeletedWithName": "Uzanto {0} estis forigita",
+ "MessageServerConfigurationUpdated": "Servila agordaro estis ĝisdatigita",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Servila agorda sekcio {0} estis ĝisdatigita",
+ "MessageApplicationUpdatedTo": "Jellyfin Server estis ĝisdatigita al {0}",
+ "MessageApplicationUpdated": "Jellyfin Server estis ĝisdatigita",
+ "TaskRefreshChannelsDescription": "Refreŝigas informon pri interretaj kanaloj.",
+ "TaskDownloadMissingSubtitles": "Elŝutu mankantajn subtekstojn",
+ "TaskCleanTranscode": "Malplenigi Transkodadan Katalogon",
+ "TaskRefreshChapterImages": "Eltiru Ĉapitro-Bildojn",
+ "TaskCleanCache": "Malplenigi Staplan Katalogon",
+ "TaskCleanActivityLog": "Malplenigi Aktivecan Ĵurnalon",
+ "PluginUpdatedWithName": "{0} estis ĝisdatigita",
+ "NotificationOptionVideoPlaybackStopped": "La videoludado haltis",
+ "NotificationOptionUserLockedOut": "Uzanto ŝlosita",
+ "NotificationOptionTaskFailed": "Planita tasko malsukcesis",
+ "NotificationOptionPluginUpdateInstalled": "Ĝisdatigo de kromprogramo instalita",
+ "NotificationOptionCameraImageUploaded": "Kamera bildo alŝutita",
+ "NotificationOptionApplicationUpdateInstalled": "Aplikaĵa ĝisdatigo instalita",
+ "NotificationOptionApplicationUpdateAvailable": "Ĝisdatigo de aplikaĵo havebla",
+ "LabelRunningTimeValue": "Ludada tempo: {0}",
+ "HeaderRecordingGroups": "Rikordadaj Grupoj",
+ "FailedLoginAttemptWithUserName": "Malsukcesa ensaluta provo de {0}",
+ "CameraImageUploadedFrom": "Nova kamera bildo estis alŝutita de {0}",
+ "AuthenticationSucceededWithUserName": "{0} sukcese aŭtentikigis"
}
diff --git a/Emby.Server.Implementations/Localization/Core/et.json b/Emby.Server.Implementations/Localization/Core/et.json
new file mode 100644
index 000000000..c3596ecf1
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/et.json
@@ -0,0 +1,118 @@
+{
+ "TaskCleanActivityLogDescription": "Kustutab määratud ajast vanemad tegevuslogi kirjed.",
+ "UserDownloadingItemWithValues": "{0} laeb alla {1}",
+ "HeaderRecordingGroups": "Salvestusrühmad",
+ "TaskOptimizeDatabaseDescription": "Tihendab ja puhastab andmebaasi. Selle toimingu tegemine pärast meediakogu andmebaasiga seotud muudatuste skannimist võib jõudlust parandada.",
+ "TaskOptimizeDatabase": "Optimeeri andmebaasi",
+ "TaskDownloadMissingSubtitlesDescription": "Otsib veebist puuduvaid subtiitreid vastavalt määratud metaandmete seadetele.",
+ "TaskDownloadMissingSubtitles": "Laadi alla puuduvad subtiitrid",
+ "TaskRefreshChannelsDescription": "Värskendab veebikanalite teavet.",
+ "TaskRefreshChannels": "Värskenda kanaleid",
+ "TaskCleanTranscodeDescription": "Kustutab üle ühe päeva vanused transkodeerimisfailid.",
+ "TaskCleanTranscode": "Puhasta transkoodimise kataloog",
+ "TaskUpdatePluginsDescription": "Laadib alla ja paigaldab nende pluginate uuendused, mis on seadistatud automaatselt uuenduma.",
+ "TaskUpdatePlugins": "Uuenda pluginaid",
+ "TaskRefreshPeopleDescription": "Värskendab meediakogus näitlejate ja režissööride metaandmeid.",
+ "TaskRefreshPeople": "Värskenda inimesi",
+ "TaskCleanLogsDescription": "Kustutab logifailid, mis on vanemad kui {0} päeva.",
+ "TaskCleanLogs": "Puhasta logikataloog",
+ "TaskRefreshLibraryDescription": "Otsib meedikogust uusi faile ja värskendab metaandmeid.",
+ "Collections": "Kollektsioonid",
+ "TaskRefreshLibrary": "Skaneeri meediakogu",
+ "TaskRefreshChapterImagesDescription": "Loob peatükkidega videote jaoks pisipildid.",
+ "TaskRefreshChapterImages": "Eralda peatükipildid",
+ "TaskCleanCacheDescription": "Kustutab vahemälufailid, mida süsteem enam ei vaja.",
+ "TaskCleanCache": "Puhasta vahemälu kataloog",
+ "TaskCleanActivityLog": "Puhasta tegevuslogi",
+ "TasksChannelsCategory": "Veebikanalid",
+ "TasksApplicationCategory": "Rakendus",
+ "TasksLibraryCategory": "Meediakogu",
+ "TasksMaintenanceCategory": "Hooldus",
+ "VersionNumber": "Versioon {0}",
+ "ValueSpecialEpisodeName": "Eriepisood - {0}",
+ "ValueHasBeenAddedToLibrary": "{0} lisati meediakogusse",
+ "UserStartedPlayingItemWithValues": "{0} taasesitab {1} serveris {2}",
+ "UserPasswordChangedWithName": "Kasutaja {0} parooli on muudetud",
+ "UserLockedOutWithName": "Kasutaja {0} on lukustatud",
+ "UserDeletedWithName": "Kasutaja {0} on kustutatud",
+ "UserCreatedWithName": "Kasutaja {0} on loodud",
+ "ScheduledTaskStartedWithName": "{0} käivitati",
+ "ProviderValue": "Allikas: {0}",
+ "StartupEmbyServerIsLoading": "Jellyfin server laadib. Proovi varsti uuesti.",
+ "User": "Kasutaja",
+ "Undefined": "Määratlemata",
+ "TvShows": "Seriaalid",
+ "System": "Süsteem",
+ "Sync": "Sünkrooni",
+ "Songs": "Laulud",
+ "Shows": "Sarjad",
+ "ServerNameNeedsToBeRestarted": "{0} tuleb taaskäivitada",
+ "ScheduledTaskFailedWithName": "{0} nurjus",
+ "PluginUpdatedWithName": "{0} uuendati",
+ "PluginUninstalledWithName": "{0} eemaldati",
+ "PluginInstalledWithName": "{0} paigaldati",
+ "Plugin": "Plugin",
+ "Playlists": "Pleilistid",
+ "Photos": "Fotod",
+ "NotificationOptionVideoPlaybackStopped": "Video taasesitus on peatatud",
+ "NotificationOptionVideoPlayback": "Video taasesitus algas",
+ "NotificationOptionUserLockedOut": "Kasutaja on lukustatud",
+ "NotificationOptionTaskFailed": "Ajastatud ülesanne nurjus",
+ "NotificationOptionServerRestartRequired": "Vajalik on serveri taaskäivitamine",
+ "NotificationOptionPluginUpdateInstalled": "Paigaldati plugina uuendus",
+ "NotificationOptionPluginUninstalled": "Plugin eemaldati",
+ "NotificationOptionPluginInstalled": "Plugin paigaldati",
+ "NotificationOptionPluginError": "Plugina tõrge",
+ "NotificationOptionNewLibraryContent": "Lisati uut sisu",
+ "NotificationOptionInstallationFailed": "Paigaldamine nurjus",
+ "NotificationOptionCameraImageUploaded": "Kaamera pilt on üles laaditud",
+ "NotificationOptionAudioPlaybackStopped": "Heli taasesitus peatati",
+ "NotificationOptionAudioPlayback": "Heli taasesitus algas",
+ "NotificationOptionApplicationUpdateInstalled": "Rakenduse uuendus paigaldati",
+ "NotificationOptionApplicationUpdateAvailable": "Rakenduse uuendus on saadaval",
+ "NewVersionIsAvailable": "Jellyfin serveri uus versioon on allalaadimiseks saadaval.",
+ "NameSeasonUnknown": "Tundmatu hooaeg",
+ "NameSeasonNumber": "Hooaeg {0}",
+ "NameInstallFailed": "{0} paigaldamine nurjus",
+ "MusicVideos": "Muusikavideod",
+ "Music": "Muusika",
+ "Movies": "Filmid",
+ "MixedContent": "Segatud sisu",
+ "MessageServerConfigurationUpdated": "Serveri seadistust uuendati",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Serveri seadistusosa {0} uuendati",
+ "MessageApplicationUpdatedTo": "Jellyfin server uuendati versioonile {0}",
+ "MessageApplicationUpdated": "Jellyfin server uuendati",
+ "Latest": "Uusimad",
+ "LabelRunningTimeValue": "Kestus: {0}",
+ "LabelIpAddressValue": "IP aadress: {0}",
+ "ItemRemovedWithName": "{0} eemaldati meediakogust",
+ "ItemAddedWithName": "{0} lisati meediakogusse",
+ "Inherit": "Päri",
+ "HomeVideos": "Koduvideod",
+ "HeaderNextUp": "Järgmisena",
+ "HeaderLiveTV": "Otse TV",
+ "HeaderFavoriteSongs": "Lemmiklood",
+ "HeaderFavoriteShows": "Lemmikseriaalid",
+ "HeaderFavoriteEpisodes": "Lemmikepisoodid",
+ "HeaderFavoriteArtists": "Lemmikesitajad",
+ "HeaderFavoriteAlbums": "Lemmikalbumid",
+ "HeaderContinueWatching": "Jätka vaatamist",
+ "HeaderAlbumArtists": "Albumi esitaja",
+ "Genres": "Žanrid",
+ "Forced": "Sunnitud",
+ "Folders": "Kaustad",
+ "Favorites": "Lemmikud",
+ "FailedLoginAttemptWithUserName": "Ebaõnnestunud sisselogimiskatse kasutajalt {0}",
+ "DeviceOnlineWithName": "{0} on ühendatud",
+ "DeviceOfflineWithName": "{0} katkestas ühenduse",
+ "Default": "Vaikimisi",
+ "ChapterNameValue": "Peatükk {0}",
+ "Channels": "Kanalid",
+ "CameraImageUploadedFrom": "Uus kaamera pilt laaditi üles allikalt {0}",
+ "Books": "Raamatud",
+ "AuthenticationSucceededWithUserName": "{0} autentimine õnnestus",
+ "Artists": "Esitajad",
+ "Application": "Rakendus",
+ "AppDeviceValues": "Rakendus: {0}, seade: {1}",
+ "Albums": "Albumid"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/nb.json b/Emby.Server.Implementations/Localization/Core/nb.json
index 81c1eefe7..317bdcfcb 100644
--- a/Emby.Server.Implementations/Localization/Core/nb.json
+++ b/Emby.Server.Implementations/Localization/Core/nb.json
@@ -119,5 +119,6 @@
"Forced": "Tvunget",
"Default": "Standard",
"TaskCleanActivityLogDescription": "Sletter oppføringer i aktivitetsloggen som er eldre enn den konfigurerte alderen.",
- "TaskOptimizeDatabase": "Optimiser database"
+ "TaskOptimizeDatabase": "Optimiser database",
+ "TaskOptimizeDatabaseDescription": "Komprimerer database og frigjør plass. Denne prosessen kan forbedre ytelsen etter skanning av bibliotek eller andre handlinger som fører til databaseendringer."
}
diff --git a/Emby.Server.Implementations/Localization/Core/pr.json b/Emby.Server.Implementations/Localization/Core/pr.json
index 0967ef424..e3a3bfaf1 100644
--- a/Emby.Server.Implementations/Localization/Core/pr.json
+++ b/Emby.Server.Implementations/Localization/Core/pr.json
@@ -1 +1,5 @@
-{}
+{
+ "Books": "Libros",
+ "AuthenticationSucceededWithUserName": "{0} autentificado correctamente",
+ "Artists": "Artistas"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/uk.json b/Emby.Server.Implementations/Localization/Core/uk.json
index 5a2069df5..e99ed6f0b 100644
--- a/Emby.Server.Implementations/Localization/Core/uk.json
+++ b/Emby.Server.Implementations/Localization/Core/uk.json
@@ -1,5 +1,5 @@
{
- "MusicVideos": "Музичні відеокліпи",
+ "MusicVideos": "Відеокліпи",
"Music": "Музика",
"Movies": "Фільми",
"MessageApplicationUpdatedTo": "Jellyfin Server оновлено до версії {0}",
@@ -16,7 +16,7 @@
"HeaderFavoriteArtists": "Улюблені виконавці",
"HeaderFavoriteAlbums": "Улюблені альбоми",
"HeaderContinueWatching": "Продовжити перегляд",
- "HeaderAlbumArtists": "Виконавці альбому",
+ "HeaderAlbumArtists": "Виконавці альбомів",
"Genres": "Жанри",
"Folders": "Каталоги",
"Favorites": "Улюблені",
@@ -38,7 +38,7 @@
"NotificationOptionPluginInstalled": "Плагін встановлено",
"NotificationOptionPluginError": "Помилка плагіна",
"NotificationOptionNewLibraryContent": "Додано новий контент",
- "HomeVideos": "Домашнє відео",
+ "HomeVideos": "Мої відео",
"FailedLoginAttemptWithUserName": "Невдала спроба входу від {0}",
"LabelRunningTimeValue": "Тривалість: {0}",
"TaskDownloadMissingSubtitlesDescription": "Шукає в Інтернеті відсутні субтитри на основі конфігурації метаданих.",
@@ -117,5 +117,7 @@
"TaskCleanActivityLogDescription": "Видаляє старші за встановлений термін записи з журналу активності.",
"TaskCleanActivityLog": "Очистити журнал активності",
"Undefined": "Не визначено",
- "Default": "За замовчуванням"
+ "Default": "За замовчуванням",
+ "TaskOptimizeDatabase": "Оптимізувати базу даних",
+ "TaskOptimizeDatabaseDescription": "Стиснення бази даних та збільшення вільного простору. Виконання цього завдання після сканування бібліотеки або внесення інших змін, які передбачають модифікацію бази даних, може покращити продуктивність."
}
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 8a33b12f4..35921ede8 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -183,36 +183,5 @@ namespace Jellyfin.Api.Controllers
{
return Path.Combine(_applicationPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
}
-
- /// <summary>
- /// Downloads the image.
- /// </summary>
- /// <param name="url">The URL.</param>
- /// <param name="urlHash">The URL hash.</param>
- /// <param name="pointerCachePath">The pointer cache path.</param>
- /// <returns>Task.</returns>
- private async Task DownloadImage(Uri url, Guid urlHash, string pointerCachePath)
- {
- var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
- using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
- if (response.Content.Headers.ContentType?.MediaType == null)
- {
- throw new ResourceNotFoundException(nameof(response.Content.Headers.ContentType));
- }
-
- var ext = response.Content.Headers.ContentType.MediaType.AsSpan().RightPart('/').ToString();
- var fullCachePath = GetFullCachePath(urlHash + "." + ext);
-
- var fullCacheDirectory = Path.GetDirectoryName(fullCachePath) ?? throw new ResourceNotFoundException($"Provided path ({fullCachePath}) is not valid.");
- Directory.CreateDirectory(fullCacheDirectory);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var fileStream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
- await response.Content.CopyToAsync(fileStream).ConfigureAwait(false);
-
- var pointerCacheDirectory = Path.GetDirectoryName(pointerCachePath) ?? throw new ArgumentException($"Provided path ({pointerCachePath}) is not valid.", nameof(pointerCachePath));
- Directory.CreateDirectory(pointerCacheDirectory);
- await System.IO.File.WriteAllTextAsync(pointerCachePath, fullCachePath, CancellationToken.None)
- .ConfigureAwait(false);
- }
}
}
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 8fb85c732..db8307f28 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -127,7 +127,7 @@ namespace Jellyfin.Api.Controllers
{
var video = (Video)_libraryManager.GetItemById(itemId);
- return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, CancellationToken.None).ConfigureAwait(false);
+ return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 488856c4e..07d0b5543 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
@@ -607,6 +608,10 @@ namespace Jellyfin.Api.Helpers
{
StartThrottler(state, transcodingJob);
}
+ else if (transcodingJob.ExitCode != 0)
+ {
+ throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "FFmpeg exited with code {0}", transcodingJob.ExitCode));
+ }
_logger.LogDebug("StartFfMpeg() finished successfully");
@@ -743,6 +748,7 @@ namespace Jellyfin.Api.Helpers
private void OnFfMpegProcessExited(Process process, TranscodingJobDto job, StreamState state)
{
job.HasExited = true;
+ job.ExitCode = process.ExitCode;
_logger.LogDebug("Disposing stream resources");
state.Dispose();
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index cdc69618a..57480b2f3 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -13,10 +13,10 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0-rc.1.21452.15" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.2" />
- <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.2" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+ <PackageReference Include="Swashbuckle.AspNetCore.ReDoc" Version="6.2.3" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
index 291e571dc..fed837b85 100644
--- a/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
+++ b/Jellyfin.Api/Models/PlaybackDtos/TranscodingJobDto.cs
@@ -107,6 +107,11 @@ namespace Jellyfin.Api.Models.PlaybackDtos
public bool HasExited { get; set; }
/// <summary>
+ /// Gets or sets exit code.
+ /// </summary>
+ public int ExitCode { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether is user paused.
/// </summary>
public bool IsUserPaused { get; set; }
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index f1bfaa63e..2de53e7c8 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -35,7 +35,7 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.2*" />
</ItemGroup>
<ItemGroup>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index d9e6d794b..e26cf093b 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -19,13 +19,13 @@
<ItemGroup>
<PackageReference Include="System.Linq.Async" Version="5.0.0" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-rc.1.21452.10" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.1.21452.10" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.1.21452.10">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.0-rc.2*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.1.21452.10">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.0-rc.2*">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 6603105fa..8983eb50f 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -31,15 +31,15 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0-rc.1.21452.15" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0-rc.1.21452.15" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="6.0.0-rc.2*" />
<PackageReference Include="prometheus-net" Version="5.0.1" />
<PackageReference Include="prometheus-net.AspNetCore" Version="5.0.1" />
<PackageReference Include="Serilog.AspNetCore" Version="4.1.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
- <PackageReference Include="Serilog.Settings.Configuration" Version="3.2.0" />
+ <PackageReference Include="Serilog.Settings.Configuration" Version="3.3.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
diff --git a/MediaBrowser.MediaEncoding/FfmpegException.cs b/MediaBrowser.Common/FfmpegException.cs
index 1697fd33a..be420196d 100644
--- a/MediaBrowser.MediaEncoding/FfmpegException.cs
+++ b/MediaBrowser.Common/FfmpegException.cs
@@ -1,6 +1,6 @@
using System;
-namespace MediaBrowser.MediaEncoding
+namespace MediaBrowser.Common
{
/// <summary>
/// Represents errors that occur during interaction with FFmpeg.
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index c87d58a14..9c8ce4ac5 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -19,8 +19,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 786ebe333..d37880865 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -14,11 +14,11 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Diacritics" Version="2.1.20036.1" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Diacritics" Version="3.3.4" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0-rc.2*" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index c5522bc3c..e6511ca8d 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="mediaSource">Media source information.</param>
/// <param name="imageStream">Media stream information.</param>
/// <param name="imageStreamIndex">Index of the stream to extract from.</param>
+ /// <param name="outputExtension">The extension of the file to write, including the '.'.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
- Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
+ Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken);
/// <summary>
/// Extracts the video images on interval.
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 3330dd540..52aa44024 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -31,12 +31,14 @@ namespace MediaBrowser.Controller.Subtitles
/// <param name="video">The video.</param>
/// <param name="language">Subtitle language.</param>
/// <param name="isPerfectMatch">Require perfect match.</param>
+ /// <param name="isAutomated">Request is automated.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>Subtitles, wrapped in task.</returns>
Task<RemoteSubtitleInfo[]> SearchSubtitles(
Video video,
string language,
bool? isPerfectMatch,
+ bool isAutomated,
CancellationToken cancellationToken);
/// <summary>
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index 767d87d46..ef052237a 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -51,5 +51,7 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
+
+ public bool IsAutomated { get; set; }
}
}
diff --git a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
index 6f66fd61b..5e7af23fc 100644
--- a/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
+++ b/MediaBrowser.LocalMetadata/Savers/BaseXmlSaver.cs
@@ -113,11 +113,19 @@ namespace MediaBrowser.LocalMetadata.Savers
{
var directory = Path.GetDirectoryName(path) ?? throw new ArgumentException($"Provided path ({path}) is not valid.", nameof(path));
Directory.CreateDirectory(directory);
+
// On Windows, savint the file will fail if the file is hidden or readonly
FileSystem.SetAttributes(path, false, false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ var fileStreamOptions = new FileStreamOptions()
+ {
+ Mode = FileMode.Create,
+ Access = FileAccess.Write,
+ Share = FileShare.None,
+ PreallocationSize = stream.Length
+ };
+
+ using (var filestream = new FileStream(path, fileStreamOptions))
{
stream.CopyTo(filestream);
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 06fe95ce8..fbc7ba72f 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -12,6 +12,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Extensions.Json;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
@@ -146,6 +147,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <param name="pathType">The path type.</param>
public void UpdateEncoderPath(string path, string pathType)
{
+ var config = _configurationManager.GetEncodingOptions();
+
+ // Filesystem may not be case insensitive, but EncoderAppPathDisplay should always point to a valid file?
+ if (string.IsNullOrEmpty(config.EncoderAppPath)
+ && string.Equals(config.EncoderAppPathDisplay, path, StringComparison.OrdinalIgnoreCase))
+ {
+ _logger.LogDebug("Existing ffmpeg path is empty and the new path is the same as {EncoderAppPathDisplay}. Skipping", nameof(config.EncoderAppPathDisplay));
+ return;
+ }
+
string newPath;
_logger.LogInformation("Attempting to update encoder path to {Path}. pathType: {PathType}", path ?? string.Empty, pathType ?? string.Empty);
@@ -180,7 +191,6 @@ 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.GetEncodingOptions();
config.EncoderAppPath = newPath;
_configurationManager.SaveConfiguration("encoding", config);
@@ -468,17 +478,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
Protocol = MediaProtocol.File
};
- return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken);
+ return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, ".jpg", cancellationToken);
}
public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
- return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, cancellationToken);
+ return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, ".jpg", cancellationToken);
}
- public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken)
+ public Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken)
{
- return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken);
+ return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken);
}
private async Task<string> ExtractImage(
@@ -490,6 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool isAudio,
Video3DFormat? threedFormat,
TimeSpan? offset,
+ string outputExtension,
CancellationToken cancellationToken)
{
var inputArgument = GetInputArgument(inputFile, mediaSource);
@@ -499,7 +510,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -512,7 +523,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -525,7 +536,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -537,17 +548,26 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
- private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException(nameof(inputPath));
}
- var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
+ if (string.IsNullOrEmpty(outputExtension))
+ {
+ outputExtension = ".jpg";
+ }
+ else if (outputExtension[0] != '.')
+ {
+ outputExtension = "." + outputExtension;
+ }
+
+ var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension);
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 22bba2366..a6caca8db 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -24,8 +24,8 @@
<ItemGroup>
<PackageReference Include="BDInfo" Version="0.7.6.1" />
<PackageReference Include="libse" Version="3.6.2" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
+ <PackageReference Include="System.Text.Encoding.CodePages" Version="6.0.0-rc.2*" />
<PackageReference Include="UTF.Unknown" Version="2.4.0" />
</ItemGroup>
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index fee3bf12e..9279cb220 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -582,7 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing
/// <returns>MediaAttachments.</returns>
private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
{
- if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase)
+ && streamInfo.Disposition?.GetValueOrDefault("attached_pic") != 1)
{
return null;
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 022cdbe9d..2b2de2ff6 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -11,6 +11,7 @@ using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
@@ -679,7 +680,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
if (!string.Equals(text, newText, StringComparison.Ordinal))
{
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
using (var writer = new StreamWriter(fileStream, encoding))
{
diff --git a/MediaBrowser.Model/IO/AsyncFile.cs b/MediaBrowser.Model/IO/AsyncFile.cs
index caee9c528..3c8007d1c 100644
--- a/MediaBrowser.Model/IO/AsyncFile.cs
+++ b/MediaBrowser.Model/IO/AsyncFile.cs
@@ -8,6 +8,25 @@ namespace MediaBrowser.Model.IO
public static class AsyncFile
{
/// <summary>
+ /// Gets the default <see cref="FileStreamOptions"/> for reading files async.
+ /// </summary>
+ public static FileStreamOptions ReadOptions => new FileStreamOptions()
+ {
+ Options = FileOptions.Asynchronous
+ };
+
+ /// <summary>
+ /// Gets the default <see cref="FileStreamOptions"/> for writing files async.
+ /// </summary>
+ public static FileStreamOptions WriteOptions => new FileStreamOptions()
+ {
+ Mode = FileMode.OpenOrCreate,
+ Access = FileAccess.Write,
+ Share = FileShare.None,
+ Options = FileOptions.Asynchronous
+ };
+
+ /// <summary>
/// Opens an existing file for reading.
/// </summary>
/// <param name="path">The file to be opened for reading.</param>
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index e6a5b1711..16bc4adf8 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -30,9 +30,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0-rc.2*" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
- <PackageReference Include="System.Text.Json" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="System.Text.Json" Version="6.0.0-rc.2*" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs
index 4b05edd67..3d8dd1486 100644
--- a/MediaBrowser.Providers/Manager/ImageSaver.cs
+++ b/MediaBrowser.Providers/Manager/ImageSaver.cs
@@ -261,8 +261,10 @@ namespace MediaBrowser.Providers.Manager
_fileSystem.SetAttributes(path, false, false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous))
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = source.Length;
+ await using (var fs = new FileStream(path, fileStreamOptions))
{
await source.CopyToAsync(fs, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 73bca5aa5..3593697d7 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -163,7 +163,7 @@ namespace MediaBrowser.Providers.Manager
{
var mimeType = MimeTypes.GetMimeType(response.Path);
- var stream = new FileStream(response.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ var stream = AsyncFile.OpenRead(response.Path);
await _providerManager.SaveImage(item, stream, mimeType, imageType, null, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index b51a25417..9be887e9c 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -210,8 +210,7 @@ namespace MediaBrowser.Providers.Manager
throw new ArgumentNullException(nameof(source));
}
- var fileStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
-
+ var fileStream = AsyncFile.OpenRead(source);
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 71a3554fd..15badfad7 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,9 +16,9 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0-rc.1.21451.13" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0-rc.2*" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
new file mode 100644
index 000000000..1d9d1e02a
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -0,0 +1,220 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ /// <summary>
+ /// Uses <see cref="IMediaEncoder"/> to extract embedded images.
+ /// </summary>
+ public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder
+ {
+ private static readonly string[] _primaryImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default"
+ };
+
+ private static readonly string[] _backdropImageFileNames =
+ {
+ "backdrop",
+ "fanart",
+ "background",
+ "art"
+ };
+
+ private static readonly string[] _logoImageFileNames =
+ {
+ "logo",
+ };
+
+ private readonly IMediaEncoder _mediaEncoder;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EmbeddedImageProvider"/> class.
+ /// </summary>
+ /// <param name="mediaEncoder">The media encoder for extracting attached/embedded images.</param>
+ public EmbeddedImageProvider(IMediaEncoder mediaEncoder)
+ {
+ _mediaEncoder = mediaEncoder;
+ }
+
+ /// <inheritdoc />
+ public string Name => "Embedded Image Extractor";
+
+ /// <inheritdoc />
+ // Default to after internet image providers but before Screen Grabber
+ public int Order => 99;
+
+ /// <inheritdoc />
+ public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
+ {
+ if (item is Video)
+ {
+ if (item is Episode)
+ {
+ return new[]
+ {
+ ImageType.Primary,
+ };
+ }
+
+ return new[]
+ {
+ ImageType.Primary,
+ ImageType.Backdrop,
+ ImageType.Logo,
+ };
+ }
+
+ return Array.Empty<ImageType>();
+ }
+
+ /// <inheritdoc />
+ public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
+ {
+ var video = (Video)item;
+
+ // No support for these
+ if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd)
+ {
+ return Task.FromResult(new DynamicImageResponse { HasImage = false });
+ }
+
+ return GetEmbeddedImage(video, type, cancellationToken);
+ }
+
+ private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType type, CancellationToken cancellationToken)
+ {
+ MediaSourceInfo mediaSource = new MediaSourceInfo
+ {
+ VideoType = item.VideoType,
+ IsoType = item.IsoType,
+ Protocol = item.PathProtocol ?? MediaProtocol.File,
+ };
+
+ string[] imageFileNames = type switch
+ {
+ ImageType.Primary => _primaryImageFileNames,
+ ImageType.Backdrop => _backdropImageFileNames,
+ ImageType.Logo => _logoImageFileNames,
+ _ => _primaryImageFileNames
+ };
+
+ // Try attachments first
+ var attachmentStream = item.GetMediaSources(false)
+ .SelectMany(source => source.MediaAttachments)
+ .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName)
+ && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ if (attachmentStream != null)
+ {
+ return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource);
+ }
+
+ // Fall back to EmbeddedImage streams
+ var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage);
+
+ if (imageStreams.Count == 0)
+ {
+ // Can't extract if we don't have any EmbeddedImage streams
+ return new DynamicImageResponse { HasImage = false };
+ }
+
+ // Extract first stream containing an element of imageFileNames
+ var imageStream = imageStreams
+ .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment)
+ && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ // Primary type only: default to first image if none found by label
+ if (imageStream == null)
+ {
+ if (type == ImageType.Primary)
+ {
+ imageStream = imageStreams[0];
+ }
+ else
+ {
+ // No streams matched, abort
+ return new DynamicImageResponse { HasImage = false };
+ }
+ }
+
+ string extractedImagePath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken)
+ .ConfigureAwait(false);
+
+ return new DynamicImageResponse
+ {
+ Format = ImageFormat.Jpg,
+ HasImage = true,
+ Path = extractedImagePath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ private async Task<DynamicImageResponse> ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource)
+ {
+ var extension = string.IsNullOrEmpty(attachmentStream.MimeType)
+ ? Path.GetExtension(attachmentStream.FileName)
+ : MimeTypes.ToExtension(attachmentStream.MimeType);
+
+ if (string.IsNullOrEmpty(extension))
+ {
+ extension = ".jpg";
+ }
+
+ string extractedAttachmentPath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken)
+ .ConfigureAwait(false);
+
+ ImageFormat format = extension switch
+ {
+ ".bmp" => ImageFormat.Bmp,
+ ".gif" => ImageFormat.Gif,
+ ".jpg" => ImageFormat.Jpg,
+ ".png" => ImageFormat.Png,
+ ".webp" => ImageFormat.Webp,
+ _ => ImageFormat.Jpg
+ };
+
+ return new DynamicImageResponse
+ {
+ Format = format,
+ HasImage = true,
+ Path = extractedAttachmentPath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ /// <inheritdoc />
+ public bool Supports(BaseItem item)
+ {
+ if (item.IsShortcut)
+ {
+ return false;
+ }
+
+ if (!item.IsFileProtocol)
+ {
+ return false;
+ }
+
+ return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia;
+ }
+ }
+}
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index 1f17d8cd4..261c012a1 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -557,6 +557,7 @@ namespace MediaBrowser.Providers.MediaInfo
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
+ true,
cancellationToken).ConfigureAwait(false);
// Rescan
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
index 449f0d259..cba2f1c42 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleDownloader.cs
@@ -36,6 +36,7 @@ namespace MediaBrowser.Providers.MediaInfo
IEnumerable<string> languages,
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
+ bool isAutomated,
CancellationToken cancellationToken)
{
var downloadedLanguages = new List<string>();
@@ -51,6 +52,7 @@ namespace MediaBrowser.Providers.MediaInfo
lang,
disabledSubtitleFetchers,
subtitleFetcherOrder,
+ isAutomated,
cancellationToken).ConfigureAwait(false);
if (downloaded)
@@ -71,6 +73,7 @@ namespace MediaBrowser.Providers.MediaInfo
string lang,
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
+ bool isAutomated,
CancellationToken cancellationToken)
{
if (video.VideoType != VideoType.VideoFile)
@@ -109,6 +112,7 @@ namespace MediaBrowser.Providers.MediaInfo
disabledSubtitleFetchers,
subtitleFetcherOrder,
mediaType,
+ isAutomated,
cancellationToken);
}
@@ -122,6 +126,7 @@ namespace MediaBrowser.Providers.MediaInfo
string[] disabledSubtitleFetchers,
string[] subtitleFetcherOrder,
VideoContentType mediaType,
+ bool isAutomated,
CancellationToken cancellationToken)
{
// There's already subtitles for this language
@@ -169,7 +174,8 @@ namespace MediaBrowser.Providers.MediaInfo
IsPerfectMatch = requirePerfectMatch,
DisabledSubtitleFetchers = disabledSubtitleFetchers,
- SubtitleFetcherOrder = subtitleFetcherOrder
+ SubtitleFetcherOrder = subtitleFetcherOrder,
+ IsAutomated = isAutomated
};
if (video is Episode episode)
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
index 9804ec3bb..4dfe2c59f 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleScheduledTask.cs
@@ -197,6 +197,7 @@ namespace MediaBrowser.Providers.MediaInfo
subtitleDownloadLanguages,
libraryOptions.DisabledSubtitleFetchers,
libraryOptions.SubtitleFetcherOrder,
+ true,
cancellationToken).ConfigureAwait(false);
// Rescan
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 8b96205c2..d226182c0 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -1,6 +1,3 @@
-#nullable enable
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Linq;
@@ -17,11 +14,19 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
+ /// <summary>
+ /// Uses <see cref="IMediaEncoder"/> to create still images from the main video.
+ /// </summary>
public class VideoImageProvider : IDynamicImageProvider, IHasOrder
{
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger<VideoImageProvider> _logger;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VideoImageProvider"/> class.
+ /// </summary>
+ /// <param name="mediaEncoder">The media encoder for capturing images.</param>
+ /// <param name="logger">The logger.</param>
public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger)
{
_mediaEncoder = mediaEncoder;
@@ -71,36 +76,22 @@ namespace MediaBrowser.Providers.MediaInfo
Protocol = item.PathProtocol ?? MediaProtocol.File,
};
- var mediaStreams =
- item.GetMediaStreams();
-
- var imageStreams =
- mediaStreams
- .Where(i => i.Type == MediaStreamType.EmbeddedImage)
- .ToList();
+ // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
+ // Always use 10 seconds for dvd because our duration could be out of whack
+ var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
+ item.RunTimeTicks.Value > 0
+ ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10)
+ : TimeSpan.FromSeconds(10);
- string extractedImagePath;
+ var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video);
- if (imageStreams.Count == 0)
+ if (videoStream == null)
{
- // If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
- // Always use 10 seconds for dvd because our duration could be out of whack
- var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
- item.RunTimeTicks.Value > 0
- ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10)
- : TimeSpan.FromSeconds(10);
-
- var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
+ _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty);
+ return new DynamicImageResponse { HasImage = false };
}
- else
- {
- var imageStream = imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("front", StringComparison.OrdinalIgnoreCase))
- ?? imageStreams.Find(i => (i.Comment ?? string.Empty).Contains("cover", StringComparison.OrdinalIgnoreCase))
- ?? imageStreams[0];
- extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false);
- }
+ string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
return new DynamicImageResponse
{
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
index 3e0b0014e..ebdcb94eb 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbAlbumProvider.cs
@@ -172,8 +172,11 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(url, cancellationToken).ConfigureAwait(false);
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = stream.Length;
+ await using var xmlFileStream = new FileStream(path, fileStreamOptions);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
index e0b2f9c58..321013b17 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
+++ b/MediaBrowser.Providers/Plugins/AudioDb/AudioDbArtistProvider.cs
@@ -154,8 +154,10 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
Directory.CreateDirectory(Path.GetDirectoryName(path));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- await using var xmlFileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
+ var fileStreamOptions = AsyncFile.WriteOptions;
+ fileStreamOptions.Mode = FileMode.Create;
+ fileStreamOptions.PreallocationSize = stream.Length;
+ await using var xmlFileStream = new FileStream(path, fileStreamOptions);
await stream.CopyToAsync(xmlFileStream, cancellationToken).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
index 7b2454efc..a1873eaae 100644
--- a/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Studios/StudiosImageProvider.cs
@@ -172,19 +172,16 @@ namespace MediaBrowser.Providers.Studios
public IEnumerable<string> GetAvailableImages(string file)
{
- using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
+ using var fileStream = File.OpenRead(file);
using var reader = new StreamReader(fileStream);
- var lines = new List<string>();
foreach (var line in reader.ReadAllLines())
{
if (!string.IsNullOrWhiteSpace(line))
{
- lines.Add(line);
+ yield return line;
}
}
-
- return lines;
}
}
}
diff --git a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
index 78ca76e4b..73f9d7b62 100644
--- a/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
+++ b/MediaBrowser.Providers/Subtitles/SubtitleManager.cs
@@ -244,8 +244,10 @@ namespace MediaBrowser.Providers.Subtitles
{
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using var fs = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None, FileStreamBufferSize, FileOptions.Asynchronous);
+ var fileOptions = AsyncFile.WriteOptions;
+ fileOptions.Mode = FileMode.CreateNew;
+ fileOptions.PreallocationSize = stream.Length;
+ using var fs = new FileStream(savePath, fileOptions);
await stream.CopyToAsync(fs).ConfigureAwait(false);
return;
@@ -272,7 +274,7 @@ namespace MediaBrowser.Providers.Subtitles
}
/// <inheritdoc />
- public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, CancellationToken cancellationToken)
+ public Task<RemoteSubtitleInfo[]> SearchSubtitles(Video video, string language, bool? isPerfectMatch, bool isAutomated, CancellationToken cancellationToken)
{
if (video.VideoType != VideoType.VideoFile)
{
@@ -306,7 +308,8 @@ namespace MediaBrowser.Providers.Subtitles
ProductionYear = video.ProductionYear,
ProviderIds = video.ProviderIds,
RuntimeTicks = video.RunTimeTicks,
- IsPerfectMatch = isPerfectMatch ?? false
+ IsPerfectMatch = isPerfectMatch ?? false,
+ IsAutomated = isAutomated
};
if (video is Episode episode)
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index 38726a6f0..594402258 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -203,8 +203,15 @@ namespace MediaBrowser.XbmcMetadata.Savers
// On Windows, saving the file will fail if the file is hidden or readonly
FileSystem.SetAttributes(path, false, false);
- // use FileShare.None as this bypasses dotnet bug dotnet/runtime#42790 .
- using (var filestream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ var fileStreamOptions = new FileStreamOptions()
+ {
+ Mode = FileMode.Create,
+ Access = FileAccess.Write,
+ Share = FileShare.None,
+ PreallocationSize = stream.Length
+ };
+
+ using (var filestream = new FileStream(path, fileStreamOptions))
{
stream.CopyTo(filestream);
}
diff --git a/deployment/Dockerfile.centos.amd64 b/deployment/Dockerfile.centos.amd64
index 178f94f71..78f051e4f 100644
--- a/deployment/Dockerfile.centos.amd64
+++ b/deployment/Dockerfile.centos.amd64
@@ -13,7 +13,7 @@ RUN yum update -yq \
&& yum install -yq @buildsys-build rpmdevtools yum-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel git wget
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.fedora.amd64 b/deployment/Dockerfile.fedora.amd64
index f0f2977a4..14eeb6eed 100644
--- a/deployment/Dockerfile.fedora.amd64
+++ b/deployment/Dockerfile.fedora.amd64
@@ -12,7 +12,7 @@ RUN dnf update -yq \
&& dnf install -yq @buildsys-build rpmdevtools git dnf-plugins-core libcurl-devel fontconfig-devel freetype-devel openssl-devel glibc-devel libicu-devel systemd wget
# Install DotNET SDK
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.amd64 b/deployment/Dockerfile.ubuntu.amd64
index fe1b4981b..8733be89c 100644
--- a/deployment/Dockerfile.ubuntu.amd64
+++ b/deployment/Dockerfile.ubuntu.amd64
@@ -17,7 +17,7 @@ RUN apt-get update -yqq \
libfreetype6-dev libssl-dev libssl1.1 liblttng-ust0
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.arm64 b/deployment/Dockerfile.ubuntu.arm64
index d984f5d89..6ae0d53cc 100644
--- a/deployment/Dockerfile.ubuntu.arm64
+++ b/deployment/Dockerfile.ubuntu.arm64
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/deployment/Dockerfile.ubuntu.armhf b/deployment/Dockerfile.ubuntu.armhf
index c013e6797..154388148 100644
--- a/deployment/Dockerfile.ubuntu.armhf
+++ b/deployment/Dockerfile.ubuntu.armhf
@@ -16,7 +16,7 @@ RUN apt-get update -yqq \
mmv build-essential lsb-release
# Install dotnet repository
-RUN wget -q https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
+RUN wget -q https://download.visualstudio.microsoft.com/download/pr/20283373-1d83-4879-8278-0afb7fd4035e/56f204f174743b29a656499ad0fc93c3/dotnet-sdk-6.0.100-rc.2.21505.57-linux-x64.tar.gz -O dotnet-sdk.tar.gz \
&& mkdir -p dotnet-sdk \
&& tar -xzf dotnet-sdk.tar.gz -C dotnet-sdk \
&& ln -s $( pwd )/dotnet-sdk/dotnet /usr/bin/dotnet
diff --git a/src/Jellyfin.Extensions/ReadOnlyListExtension.cs b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs
new file mode 100644
index 000000000..7785cfb49
--- /dev/null
+++ b/src/Jellyfin.Extensions/ReadOnlyListExtension.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jellyfin.Extensions
+{
+ /// <summary>
+ /// Static extensions for the <see cref="IReadOnlyList{T}"/> interface.
+ /// </summary>
+ public static class ReadOnlyListExtension
+ {
+ /// <summary>
+ /// Finds the index of the desired item.
+ /// </summary>
+ /// <param name="source">The source list.</param>
+ /// <param name="value">The value to fine.</param>
+ /// <typeparam name="T">The type of item to find.</typeparam>
+ /// <returns>Index if found, else -1.</returns>
+ public static int IndexOf<T>(this IReadOnlyList<T> source, T value)
+ {
+ if (source is IList<T> list)
+ {
+ return list.IndexOf(value);
+ }
+
+ for (int i = 0; i < source.Count; i++)
+ {
+ if (Equals(value, source[i]))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ /// <summary>
+ /// Finds the index of the predicate.
+ /// </summary>
+ /// <param name="source">The source list.</param>
+ /// <param name="match">The value to find.</param>
+ /// <typeparam name="T">The type of item to find.</typeparam>
+ /// <returns>Index if found, else -1.</returns>
+ public static int FindIndex<T>(this IReadOnlyList<T> source, Predicate<T> match)
+ {
+ if (source is List<T> list)
+ {
+ return list.FindIndex(match);
+ }
+
+ for (int i = 0; i < source.Count; i++)
+ {
+ if (match(source[i]))
+ {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 8b581857f..922b3d94f 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,8 +15,8 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.1.21452.15" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
index d0d472e4d..4504924cb 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/Probing/ProbeResultNormalizerTests.cs
@@ -5,8 +5,10 @@ using System.Text.Json;
using Jellyfin.Extensions.Json;
using MediaBrowser.MediaEncoding.Probing;
using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
using Xunit;
namespace Jellyfin.MediaEncoding.Tests.Probing
@@ -56,6 +58,72 @@ namespace Jellyfin.MediaEncoding.Tests.Probing
}
[Fact]
+ public void GetMediaInfo_Mp4MetaData_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/Probing/video_mp4_metadata.json");
+ var internalMediaInfoResult = JsonSerializer.Deserialize<InternalMediaInfoResult>(bytes, _jsonOptions);
+
+ // subtitle handling requires a localization object, set a mock to return the input string
+ var mockLocalization = new Mock<ILocalizationManager>();
+ mockLocalization.Setup(x => x.GetLocalizedString(It.IsAny<string>())).Returns<string>(x => x);
+ ProbeResultNormalizer localizedProbeResultNormalizer = new ProbeResultNormalizer(new NullLogger<EncoderValidatorTests>(), mockLocalization.Object);
+
+ MediaInfo res = localizedProbeResultNormalizer.GetMediaInfo(internalMediaInfoResult, VideoType.VideoFile, false, "Test Data/Probing/video_mp4_metadata.mkv", MediaProtocol.File);
+
+ // [Video, Audio (Main), Audio (Commentary), Subtitle (Main, Spanish), Subtitle (Main, English), Subtitle (Commentary)
+ Assert.Equal(6, res.MediaStreams.Count);
+
+ Assert.NotNull(res.VideoStream);
+ Assert.Equal(res.MediaStreams[0], res.VideoStream);
+ Assert.Equal(0, res.VideoStream.Index);
+ Assert.Equal("h264", res.VideoStream.Codec);
+ Assert.Equal("High", res.VideoStream.Profile);
+ Assert.Equal(MediaStreamType.Video, res.VideoStream.Type);
+ Assert.Equal(358, res.VideoStream.Height);
+ Assert.Equal(720, res.VideoStream.Width);
+ Assert.Equal("2.40:1", res.VideoStream.AspectRatio);
+ Assert.Equal("yuv420p", res.VideoStream.PixelFormat);
+ Assert.Equal(31d, res.VideoStream.Level);
+ Assert.Equal(1, res.VideoStream.RefFrames);
+ Assert.True(res.VideoStream.IsAVC);
+ Assert.Equal(120f, res.VideoStream.RealFrameRate);
+ Assert.Equal("1/90000", res.VideoStream.TimeBase);
+ Assert.Equal(1147365, res.VideoStream.BitRate);
+ Assert.Equal(8, res.VideoStream.BitDepth);
+ Assert.True(res.VideoStream.IsDefault);
+ Assert.Equal("und", res.VideoStream.Language);
+
+ Assert.Equal(MediaStreamType.Audio, res.MediaStreams[1].Type);
+ Assert.Equal("aac", res.MediaStreams[1].Codec);
+ Assert.Equal(7, res.MediaStreams[1].Channels);
+ Assert.True(res.MediaStreams[1].IsDefault);
+ Assert.Equal("eng", res.MediaStreams[1].Language);
+ Assert.Equal("Surround 6.1", res.MediaStreams[1].Title);
+
+ Assert.Equal(MediaStreamType.Audio, res.MediaStreams[2].Type);
+ Assert.Equal("aac", res.MediaStreams[2].Codec);
+ Assert.Equal(2, res.MediaStreams[2].Channels);
+ Assert.False(res.MediaStreams[2].IsDefault);
+ Assert.Equal("eng", res.MediaStreams[2].Language);
+ Assert.Equal("Commentary", res.MediaStreams[2].Title);
+
+ Assert.Equal("spa", res.MediaStreams[3].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[3].Type);
+ Assert.Equal("DVDSUB", res.MediaStreams[3].Codec);
+ Assert.Null(res.MediaStreams[3].Title);
+
+ Assert.Equal("eng", res.MediaStreams[4].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[4].Type);
+ Assert.Equal("mov_text", res.MediaStreams[4].Codec);
+ Assert.Null(res.MediaStreams[4].Title);
+
+ Assert.Equal("eng", res.MediaStreams[5].Language);
+ Assert.Equal(MediaStreamType.Subtitle, res.MediaStreams[5].Type);
+ Assert.Equal("mov_text", res.MediaStreams[5].Codec);
+ Assert.Equal("Commentary", res.MediaStreams[5].Title);
+ }
+
+ [Fact]
public void GetMediaInfo_MusicVideo_Success()
{
var bytes = File.ReadAllBytes("Test Data/Probing/music_video_metadata.json");
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json
new file mode 100644
index 000000000..77e3def76
--- /dev/null
+++ b/tests/Jellyfin.MediaEncoding.Tests/Test Data/Probing/video_mp4_metadata.json
@@ -0,0 +1,260 @@
+{
+ "streams": [
+ {
+ "index": 0,
+ "codec_name": "h264",
+ "codec_long_name": "H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
+ "profile": "High",
+ "codec_type": "video",
+ "codec_tag_string": "avc1",
+ "codec_tag": "0x31637661",
+ "width": 720,
+ "height": 358,
+ "coded_width": 720,
+ "coded_height": 358,
+ "closed_captions": 0,
+ "has_b_frames": 2,
+ "sample_aspect_ratio": "32:27",
+ "display_aspect_ratio": "1280:537",
+ "pix_fmt": "yuv420p",
+ "level": 31,
+ "color_range": "tv",
+ "color_space": "smpte170m",
+ "color_transfer": "bt709",
+ "color_primaries": "smpte170m",
+ "chroma_location": "left",
+ "refs": 1,
+ "is_avc": "true",
+ "nal_length_size": "4",
+ "r_frame_rate": "120/1",
+ "avg_frame_rate": "1704753000/71073479",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1421469580,
+ "duration": "15794.106444",
+ "bit_rate": "1147365",
+ "bits_per_raw_sample": "8",
+ "nb_frames": "378834",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "und",
+ "handler_name": "VideoHandler",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 1,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_tag_string": "mp4a",
+ "codec_tag": "0x6134706d",
+ "sample_fmt": "fltp",
+ "sample_rate": "48000",
+ "channels": 7,
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/48000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 758115312,
+ "duration": "15794.069000",
+ "bit_rate": "224197",
+ "nb_frames": "740348",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Surround 6.1",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 2,
+ "codec_name": "aac",
+ "codec_long_name": "AAC (Advanced Audio Coding)",
+ "profile": "LC",
+ "codec_type": "audio",
+ "codec_tag_string": "mp4a",
+ "codec_tag": "0x6134706d",
+ "sample_fmt": "fltp",
+ "sample_rate": "48000",
+ "channels": 2,
+ "channel_layout": "stereo",
+ "bits_per_sample": 0,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/48000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 758114304,
+ "duration": "15794.048000",
+ "bit_rate": "160519",
+ "nb_frames": "740347",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Commentary",
+ "vendor_id": "[0][0][0][0]"
+ }
+ },
+ {
+ "index": 3,
+ "codec_name": "dvd_subtitle",
+ "codec_long_name": "DVD subtitles",
+ "codec_type": "subtitle",
+ "codec_tag_string": "mp4s",
+ "codec_tag": "0x7334706d",
+ "width": 720,
+ "height": 480,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1300301588,
+ "duration": "14447.795422",
+ "bit_rate": "2653",
+ "nb_frames": "3545",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "spa",
+ "handler_name": "SubtitleHandler"
+ }
+ },
+ {
+ "index": 4,
+ "codec_name": "mov_text",
+ "codec_long_name": "MOV text",
+ "codec_type": "subtitle",
+ "codec_tag_string": "tx3g",
+ "codec_tag": "0x67337874",
+ "width": 853,
+ "height": 51,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1401339330,
+ "duration": "15570.437000",
+ "bit_rate": "88",
+ "nb_frames": "5079",
+ "disposition": {
+ "default": 1,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "SubtitleHandler"
+ }
+ },
+ {
+ "index": 5,
+ "codec_name": "mov_text",
+ "codec_long_name": "MOV text",
+ "codec_type": "subtitle",
+ "codec_tag_string": "tx3g",
+ "codec_tag": "0x67337874",
+ "width": 853,
+ "height": 51,
+ "r_frame_rate": "0/0",
+ "avg_frame_rate": "0/0",
+ "time_base": "1/90000",
+ "start_pts": 0,
+ "start_time": "0.000000",
+ "duration_ts": 1370580300,
+ "duration": "15228.670000",
+ "bit_rate": "18",
+ "nb_frames": "1563",
+ "disposition": {
+ "default": 0,
+ "dub": 0,
+ "original": 0,
+ "comment": 0,
+ "lyrics": 0,
+ "karaoke": 0,
+ "forced": 0,
+ "hearing_impaired": 0,
+ "visual_impaired": 0,
+ "clean_effects": 0,
+ "attached_pic": 0,
+ "timed_thumbnails": 0
+ },
+ "tags": {
+ "creation_time": "2021-09-13T22:42:42.000000Z",
+ "language": "eng",
+ "handler_name": "Commentary"
+ }
+ }
+ ]
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
new file mode 100644
index 000000000..b194e3885
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
@@ -0,0 +1,216 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class EmbeddedImageProviderTests
+ {
+ private static TheoryData<BaseItem> GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData()
+ {
+ return new ()
+ {
+ new AudioBook(),
+ new BoxSet(),
+ new Series(),
+ new Season(),
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty_TestData))]
+ public void GetSupportedImages_UnsupportedBaseItems_ReturnsEmpty(BaseItem item)
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+ Assert.Empty(embeddedImageProvider.GetSupportedImages(item));
+ }
+
+ private static TheoryData<BaseItem, IEnumerable<ImageType>> GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData()
+ {
+ return new TheoryData<BaseItem, IEnumerable<ImageType>>
+ {
+ { new Episode(), new List<ImageType> { ImageType.Primary } },
+ { new Movie(), new List<ImageType> { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } },
+ };
+ }
+
+ [Theory]
+ [MemberData(nameof(GetSupportedImages_SupportedBaseItems_ReturnsPopulated_TestData))]
+ public void GetSupportedImages_SupportedBaseItems_ReturnsPopulated(BaseItem item, IEnumerable<ImageType> expected)
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+ var actual = embeddedImageProvider.GetSupportedImages(item);
+ Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString()));
+ }
+
+ [Fact]
+ public async void GetImage_InputWithNoStreams_ReturnsNoImage()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ var input = GetMovie(new List<MediaAttachment>(), new List<MediaStream>());
+
+ var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_InputWithUnlabeledAttachments_ReturnsNoImage()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ // add an attachment without a filename - has a list to look through but finds nothing
+ var input = GetMovie(
+ new List<MediaAttachment> { new () },
+ new List<MediaStream>());
+
+ var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_InputWithLabeledAttachments_ReturnsCorrectSelection()
+ {
+ // first tests file extension detection, second uses mimetype, third defaults to jpg
+ MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 };
+ MediaAttachment sampleAttachment2 = new () { FileName = "backdrop", MimeType = "image/bmp", Index = 2 };
+ MediaAttachment sampleAttachment3 = new () { FileName = "poster", Index = 3 };
+ string targetPath1 = "path1.png";
+ string targetPath2 = "path2.bmp";
+ string targetPath3 = "path2.jpg";
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), 1, ".png", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath1));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), 2, ".bmp", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath2));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), 3, ".jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath3));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(
+ new List<MediaAttachment> { sampleAttachment1, sampleAttachment2, sampleAttachment3 },
+ new List<MediaStream>());
+
+ var actualLogo = await embeddedImageProvider.GetImage(input, ImageType.Logo, CancellationToken.None);
+ Assert.NotNull(actualLogo);
+ Assert.True(actualLogo.HasImage);
+ Assert.Equal(targetPath1, actualLogo.Path);
+ Assert.Equal(ImageFormat.Png, actualLogo.Format);
+
+ var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actualBackdrop);
+ Assert.True(actualBackdrop.HasImage);
+ Assert.Equal(targetPath2, actualBackdrop.Path);
+ Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format);
+
+ var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actualPrimary);
+ Assert.True(actualPrimary.HasImage);
+ Assert.Equal(targetPath3, actualPrimary.Path);
+ Assert.Equal(ImageFormat.Jpg, actualPrimary.Format);
+ }
+
+ [Fact]
+ public async void GetImage_InputWithUnlabeledEmbeddedImages_BackdropReturnsNoImage()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ var input = GetMovie(
+ new List<MediaAttachment>(),
+ new List<MediaStream> { new () { Type = MediaStreamType.EmbeddedImage } });
+
+ var actual = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_InputWithUnlabeledEmbeddedImages_PrimaryReturnsImage()
+ {
+ MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 };
+ string targetPath = "path";
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), sampleStream, 1, ".jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(
+ new List<MediaAttachment>(),
+ new List<MediaStream> { sampleStream });
+
+ var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_InputWithLabeledEmbeddedImages_ReturnsCorrectSelection()
+ {
+ // primary is second stream to ensure it's not defaulting, backdrop is first
+ MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" };
+ MediaStream sampleStream2 = new () { Type = MediaStreamType.EmbeddedImage, Index = 2, Comment = "cover" };
+ string targetPath1 = "path1.jpg";
+ string targetPath2 = "path2.jpg";
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), sampleStream1, 1, ".jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath1));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), sampleStream2, 2, ".jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath2));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(
+ new List<MediaAttachment>(),
+ new List<MediaStream> { sampleStream1, sampleStream2 });
+
+ var actualPrimary = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actualPrimary);
+ Assert.True(actualPrimary.HasImage);
+ Assert.Equal(targetPath2, actualPrimary.Path);
+ Assert.Equal(ImageFormat.Jpg, actualPrimary.Format);
+
+ var actualBackdrop = await embeddedImageProvider.GetImage(input, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actualBackdrop);
+ Assert.True(actualBackdrop.HasImage);
+ Assert.Equal(targetPath1, actualBackdrop.Path);
+ Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format);
+ }
+
+ private static EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder)
+ {
+ return new EmbeddedImageProvider(mediaEncoder);
+ }
+
+ private static Movie GetMovie(List<MediaAttachment> mediaAttachments, List<MediaStream> mediaStreams)
+ {
+ // Mocking IMediaSourceManager GetMediaAttachments and GetMediaStreams instead of mocking Movie works, but
+ // has concurrency problems between this and VideoImageProviderTests due to BaseItem.MediaSourceManager
+ // being static
+ var movie = new Mock<Movie>();
+
+ movie.Setup(item => item.GetMediaSources(It.IsAny<bool>()))
+ .Returns(new List<MediaSourceInfo> { new () { MediaAttachments = mediaAttachments } } );
+ movie.Setup(item => item.GetMediaStreams())
+ .Returns(mediaStreams);
+
+ return movie.Object;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
new file mode 100644
index 000000000..0f51a2b8f
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class VideoImageProviderTests
+ {
+ [Fact]
+ public async void GetImage_InputIsPlaceholder_ReturnsNoImage()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ var input = new Movie
+ {
+ IsPlaceHolder = true
+ };
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_NoDefaultVideoStream_ReturnsNoImage()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ var input = new Movie
+ {
+ DefaultVideoStreamIndex = null
+ };
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_DefaultSetButNoVideoStream_ReturnsNoImage()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ // set a default index but don't put anything there (invalid input, but provider shouldn't break)
+ var input = GetMovie(0, null, new List<MediaStream>());
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_DefaultSetMultipleVideoStreams_ReturnsDefaultStreamImage()
+ {
+ MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 };
+ string targetPath = "path.jpg";
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), firstStream, It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Returns(Task.FromResult("wrong stream called!"));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), targetStream, It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(1, targetStream, new List<MediaStream> { firstStream, targetStream } );
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_InvalidDefaultSingleVideoStream_ReturnsFirstVideoStreamImage()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ string targetPath = "path.jpg";
+
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), targetStream, It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ // provide query results for default (empty) and all streams (populated)
+ var input = GetMovie(5, null, new List<MediaStream> { targetStream });
+
+ var actual = await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_NoTimeSpanSet_CallsEncoderWithDefaultTime()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+
+ // use a callback to catch the actual value
+ // provides more information on failure than verifying a specific input was called on the mock
+ TimeSpan? actualTimeSpan = null;
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Callback<string, string, MediaSourceInfo, MediaStream, Video3DFormat?, TimeSpan?, CancellationToken>((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan)
+ .Returns(Task.FromResult("path"));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(0, targetStream, new List<MediaStream> { targetStream });
+
+ // not testing return, just verifying what gets requested for time span
+ await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+
+ Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan);
+ }
+
+ [Fact]
+ public async void GetImage_TimeSpanSet_CallsEncoderWithCalculatedTime()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+
+ TimeSpan? actualTimeSpan = null;
+ var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<Video3DFormat?>(), It.IsAny<TimeSpan?>(), CancellationToken.None))
+ .Callback<string, string, MediaSourceInfo, MediaStream, Video3DFormat?, TimeSpan?, CancellationToken>((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan)
+ .Returns(Task.FromResult("path"));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = GetMovie(0, targetStream, new List<MediaStream> { targetStream });
+ input.RunTimeTicks = 5000;
+
+ // not testing return, just verifying what gets requested for time span
+ await videoImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
+
+ Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan);
+ }
+
+ private static VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder)
+ {
+ // strict to ensure this isn't accidentally used where a prepared mock is intended
+ mediaEncoder ??= new Mock<IMediaEncoder>(MockBehavior.Strict).Object;
+ return new VideoImageProvider(mediaEncoder, new NullLogger<VideoImageProvider>());
+ }
+
+ private static Movie GetMovie(int defaultVideoStreamIndex, MediaStream? defaultStream, List<MediaStream> mediaStreams)
+ {
+ // Mocking IMediaSourceManager GetMediaStreams instead of mocking Movie works, but has concurrency problems
+ // between this and EmbeddedImageProviderTests due to BaseItem.MediaSourceManager being static
+ var movie = new Mock<Movie>
+ {
+ Object =
+ {
+ DefaultVideoStreamIndex = defaultVideoStreamIndex
+ }
+ };
+
+ movie.Setup(item => item.GetDefaultVideoStream())
+ .Returns(defaultStream!);
+ movie.Setup(item => item.GetMediaStreams())
+ .Returns(mediaStreams);
+
+ return movie.Object;
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
new file mode 100644
index 000000000..3b3e38bd1
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/LiveTv/SchedulesDirect/SchedulesDirectDeserializeTests.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using Emby.Server.Implementations.LiveTv.Listings.SchedulesDirectDtos;
+using Jellyfin.Extensions.Json;
+using Xunit;
+
+namespace Jellyfin.Server.Implementations.Tests.LiveTv.SchedulesDirect
+{
+ public class SchedulesDirectDeserializeTests
+ {
+ private readonly JsonSerializerOptions _jsonOptions;
+
+ public SchedulesDirectDeserializeTests()
+ {
+ _jsonOptions = JsonDefaults.Options;
+ }
+
+ /// <summary>
+ /// /token reponse.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Token_Response_Live_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_live_response.json");
+ var tokenDto = JsonSerializer.Deserialize<TokenDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(tokenDto);
+ Assert.Equal(0, tokenDto!.Code);
+ Assert.Equal("OK", tokenDto.Message);
+ Assert.Equal("AWS-SD-web.1", tokenDto.ServerId);
+ Assert.Equal(new DateTime(2016, 08, 23, 13, 55, 25, DateTimeKind.Utc), tokenDto.TokenTimestamp);
+ Assert.Equal("f3fca79989cafe7dead71beefedc812b", tokenDto.Token);
+ }
+
+ /// <summary>
+ /// /token response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Token_Response_Offline_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/token_offline_response.json");
+ var tokenDto = JsonSerializer.Deserialize<TokenDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(tokenDto);
+ Assert.Equal(3_000, tokenDto!.Code);
+ Assert.Equal("Server offline for maintenance.", tokenDto.Message);
+ Assert.Equal("20141201.web.1", tokenDto.ServerId);
+ Assert.Equal(new DateTime(2015, 04, 23, 00, 03, 32, DateTimeKind.Utc), tokenDto.TokenTimestamp);
+ Assert.Equal("CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE", tokenDto.Token);
+ Assert.Equal("SERVICE_OFFLINE", tokenDto.Response);
+ }
+
+ /// <summary>
+ /// /schedules request.
+ /// </summary>
+ [Fact]
+ public void Serialize_Schedule_Request_Success()
+ {
+ var expectedString = File.ReadAllText("Test Data/SchedulesDirect/schedules_request.json").Trim();
+
+ var requestObject = new RequestScheduleForChannelDto[]
+ {
+ new RequestScheduleForChannelDto
+ {
+ StationId = "20454",
+ Date = new[]
+ {
+ "2015-03-13",
+ "2015-03-17"
+ }
+ },
+ new RequestScheduleForChannelDto
+ {
+ StationId = "10021",
+ Date = new[]
+ {
+ "2015-03-12",
+ "2015-03-13"
+ }
+ }
+ };
+
+ var requestString = JsonSerializer.Serialize(requestObject, _jsonOptions);
+ Assert.Equal(expectedString, requestString);
+ }
+
+ /// <summary>
+ /// /schedules response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Schedule_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/schedules_response.json");
+ var days = JsonSerializer.Deserialize<IReadOnlyList<DayDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(days);
+ Assert.Equal(1, days!.Count);
+
+ var dayDto = days[0];
+ Assert.Equal("20454", dayDto.StationId);
+ Assert.Equal(2, dayDto.Programs.Count);
+
+ Assert.Equal("SH005371070000", dayDto.Programs[0].ProgramId);
+ Assert.Equal(new DateTime(2015, 03, 03, 00, 00, 00, DateTimeKind.Utc), dayDto.Programs[0].AirDateTime);
+ Assert.Equal(1_800, dayDto.Programs[0].Duration);
+ Assert.Equal("Sy8HEMBPcuiAx3FBukUhKQ", dayDto.Programs[0].Md5);
+ Assert.True(dayDto.Programs[0].New);
+ Assert.Equal(2, dayDto.Programs[0].AudioProperties.Count);
+ Assert.Equal("stereo", dayDto.Programs[0].AudioProperties[0]);
+ Assert.Equal("cc", dayDto.Programs[0].AudioProperties[1]);
+ Assert.Equal(1, dayDto.Programs[0].VideoProperties.Count);
+ Assert.Equal("hdtv", dayDto.Programs[0].VideoProperties[0]);
+ }
+
+ /// <summary>
+ /// /programs response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Program_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/programs_response.json");
+ var programDtos = JsonSerializer.Deserialize<IReadOnlyList<ProgramDetailsDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(programDtos);
+ Assert.Equal(2, programDtos!.Count);
+ Assert.Equal("EP000000060003", programDtos[0].ProgramId);
+ Assert.Equal(1, programDtos[0].Titles.Count);
+ Assert.Equal("'Allo 'Allo!", programDtos[0].Titles[0].Title120);
+ Assert.Equal("Series", programDtos[0].EventDetails?.SubType);
+ Assert.Equal("en", programDtos[0].Descriptions?.Description1000[0].DescriptionLanguage);
+ Assert.Equal("A disguised British Intelligence officer is sent to help the airmen.", programDtos[0].Descriptions?.Description1000[0].Description);
+ Assert.Equal(new DateTime(1985, 11, 04), programDtos[0].OriginalAirDate);
+ Assert.Equal(1, programDtos[0].Genres.Count);
+ Assert.Equal("Sitcom", programDtos[0].Genres[0]);
+ Assert.Equal("The Poloceman Cometh", programDtos[0].EpisodeTitle150);
+ Assert.Equal(2, programDtos[0].Metadata[0].Gracenote?.Season);
+ Assert.Equal(3, programDtos[0].Metadata[0].Gracenote?.Episode);
+ Assert.Equal(13, programDtos[0].Cast.Count);
+ Assert.Equal("383774", programDtos[0].Cast[0].PersonId);
+ Assert.Equal("392649", programDtos[0].Cast[0].NameId);
+ Assert.Equal("Gorden Kaye", programDtos[0].Cast[0].Name);
+ Assert.Equal("Actor", programDtos[0].Cast[0].Role);
+ Assert.Equal("01", programDtos[0].Cast[0].BillingOrder);
+ Assert.Equal(3, programDtos[0].Crew.Count);
+ Assert.Equal("354407", programDtos[0].Crew[0].PersonId);
+ Assert.Equal("363281", programDtos[0].Crew[0].NameId);
+ Assert.Equal("David Croft", programDtos[0].Crew[0].Name);
+ Assert.Equal("Director", programDtos[0].Crew[0].Role);
+ Assert.Equal("01", programDtos[0].Crew[0].BillingOrder);
+ }
+
+ /// <summary>
+ /// /metadata/programs response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Metadata_Programs_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/metadata_programs_response.json");
+ var showImagesDtos = JsonSerializer.Deserialize<IReadOnlyList<ShowImagesDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(showImagesDtos);
+ Assert.Equal(1, showImagesDtos!.Count);
+ Assert.Equal("SH00712240", showImagesDtos[0].ProgramId);
+ Assert.Equal(4, showImagesDtos[0].Data.Count);
+ Assert.Equal("135", showImagesDtos[0].Data[0].Width);
+ Assert.Equal("180", showImagesDtos[0].Data[0].Height);
+ Assert.Equal("assets/p282288_b_v2_aa.jpg", showImagesDtos[0].Data[0].Uri);
+ Assert.Equal("Sm", showImagesDtos[0].Data[0].Size);
+ Assert.Equal("3x4", showImagesDtos[0].Data[0].Aspect);
+ Assert.Equal("Banner-L3", showImagesDtos[0].Data[0].Category);
+ Assert.Equal("yes", showImagesDtos[0].Data[0].Text);
+ Assert.Equal("true", showImagesDtos[0].Data[0].Primary);
+ Assert.Equal("Series", showImagesDtos[0].Data[0].Tier);
+ }
+
+ /// <summary>
+ /// /headends response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Headends_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/headends_response.json");
+ var headendsDtos = JsonSerializer.Deserialize<IReadOnlyList<HeadendsDto>>(bytes, _jsonOptions);
+
+ Assert.NotNull(headendsDtos);
+ Assert.Equal(8, headendsDtos!.Count);
+ Assert.Equal("CA00053", headendsDtos[0].Headend);
+ Assert.Equal("Cable", headendsDtos[0].Transport);
+ Assert.Equal("Beverly Hills", headendsDtos[0].Location);
+ Assert.Equal(2, headendsDtos[0].Lineups.Count);
+ Assert.Equal("Time Warner Cable - Cable", headendsDtos[0].Lineups[0].Name);
+ Assert.Equal("USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Lineup);
+ Assert.Equal("/20141201/lineups/USA-CA00053-DEFAULT", headendsDtos[0].Lineups[0].Uri);
+ }
+
+ /// <summary>
+ /// /lineups response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Lineups_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineups_response.json");
+ var lineupsDto = JsonSerializer.Deserialize<LineupsDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(lineupsDto);
+ Assert.Equal(0, lineupsDto!.Code);
+ Assert.Equal("20141201.web.1", lineupsDto.ServerId);
+ Assert.Equal(new DateTime(2015, 04, 17, 14, 22, 17, DateTimeKind.Utc), lineupsDto.LineupTimestamp);
+ Assert.Equal(5, lineupsDto.Lineups.Count);
+ Assert.Equal("GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Lineup);
+ Assert.Equal("Freeview - Carlton - LWT (Southeast)", lineupsDto.Lineups[0].Name);
+ Assert.Equal("DVB-T", lineupsDto.Lineups[0].Transport);
+ Assert.Equal("London", lineupsDto.Lineups[0].Location);
+ Assert.Equal("/20141201/lineups/GBR-0001317-DEFAULT", lineupsDto.Lineups[0].Uri);
+
+ Assert.Equal("DELETED LINEUP", lineupsDto.Lineups[4].Name);
+ Assert.True(lineupsDto.Lineups[4].IsDeleted);
+ }
+
+ /// <summary>
+ /// /lineup/:id response.
+ /// </summary>
+ [Fact]
+ public void Deserialize_Lineup_Response_Success()
+ {
+ var bytes = File.ReadAllBytes("Test Data/SchedulesDirect/lineup_response.json");
+ var channelDto = JsonSerializer.Deserialize<ChannelDto>(bytes, _jsonOptions);
+
+ Assert.NotNull(channelDto);
+ Assert.Equal(2, channelDto!.Map.Count);
+ Assert.Equal("24326", channelDto.Map[0].StationId);
+ Assert.Equal("001", channelDto.Map[0].Channel);
+ Assert.Equal("BBC ONE South", channelDto.Map[0].ProvderCallsign);
+ Assert.Equal("1", channelDto.Map[0].LogicalChannelNumber);
+ Assert.Equal("providerCallsign", channelDto.Map[0].MatchType);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json
new file mode 100644
index 000000000..015afeecc
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/headends_response.json
@@ -0,0 +1 @@
+[{"headend":"CA00053","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Time Warner Cable - Cable","lineup":"USA-CA00053-DEFAULT","uri":"/20141201/lineups/USA-CA00053-DEFAULT"},{"name":"Time Warner Cable - Digital","lineup":"USA-CA00053-X","uri":"/20141201/lineups/USA-CA00053-X"}]},{"headend":"CA61222","transport":"Cable","location":"Beverly Hills","lineups":[{"name":"Mulholland Estates - Cable","lineup":"USA-CA61222-DEFAULT","uri":"/20141201/lineups/USA-CA61222-DEFAULT"}]},{"headend":"CA66511","transport":"Cable","location":"Los Angeles","lineups":[{"name":"AT&T U-verse TV - Digital","lineup":"USA-CA66511-X","uri":"/20141201/lineups/USA-CA66511-X"}]},{"headend":"CA67309","transport":"Cable","location":"Westchester","lineups":[{"name":"Time Warner Cable Sherman Oaks - Cable","lineup":"USA-CA67309-DEFAULT","uri":"/20141201/lineups/USA-CA67309-DEFAULT"},{"name":"Time Warner Cable Sherman Oaks - Digital","lineup":"USA-CA67309-X","uri":"/20141201/lineups/USA-CA67309-X"}]},{"headend":"CA67310","transport":"Cable","location":"Eagle Rock","lineups":[{"name":"Time Warner Cable City of Los Angeles - Cable","lineup":"USA-CA67310-DEFAULT","uri":"/20141201/lineups/USA-CA67310-DEFAULT"},{"name":"Time Warner Cable City of Los Angeles - Digital","lineup":"USA-CA67310-X","uri":"/20141201/lineups/USA-CA67310-X"}]},{"headend":"DISH803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DISH Los Angeles - Satellite","lineup":"USA-DISH803-DEFAULT","uri":"/20141201/lineups/USA-DISH803-DEFAULT"}]},{"headend":"DITV803","transport":"Satellite","location":"Los Angeles","lineups":[{"name":"DIRECTV Los Angeles - Satellite","lineup":"USA-DITV803-DEFAULT","uri":"/20141201/lineups/USA-DITV803-DEFAULT"}]},{"headend":"90210","transport":"Antenna","location":"90210","lineups":[{"name":"Antenna","lineup":"USA-OTA-90210","uri":"/20141201/lineups/USA-OTA-90210"}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json
new file mode 100644
index 000000000..072089470
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineup_response.json
@@ -0,0 +1 @@
+{"map":[{"stationID":"24326","channel":"001","providerCallsign":"BBC ONE South","logicalChannelNumber":"1","matchType":"providerCallsign"},{"stationID":"17154","channel":"002","providerCallsign":"BBC TWO","logicalChannelNumber":"2","matchType":"providerCallsign"}]}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json
new file mode 100644
index 000000000..032a84e59
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/lineups_response.json
@@ -0,0 +1 @@
+{"code":0,"serverID":"20141201.web.1","datetime":"2015-04-17T14:22:17Z","lineups":[{"lineup":"GBR-0001317-DEFAULT","name":"Freeview - Carlton - LWT (Southeast)","transport":"DVB-T","location":"London","uri":"/20141201/lineups/GBR-0001317-DEFAULT"},{"lineup":"USA-IL57303-X","name":"Comcast Waukegan/Lake Forest Area - Digital","transport":"Cable","location":"Lake Forest","uri":"/20141201/lineups/USA-IL57303-X"},{"lineup":"USA-NY67791-X","name":"Verizon Fios Queens - Digital","transport":"Cable","location":"Fresh Meadows","uri":"/20141201/lineups/USA-NY67791-X"},{"lineup":"USA-OTA-60030","name":"Local Over the Air Broadcast","transport":"Antenna","location":"60030","uri":"/20141201/lineups/USA-OTA-60030"},{"lineup":"USA-WI61859-DEFAULT","name":"DELETED LINEUP","isDeleted":true}]}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json
new file mode 100644
index 000000000..78166f09a
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/metadata_programs_response.json
@@ -0,0 +1 @@
+[{"programID":"SH00712240","data":[{"width":"135","height":"180","uri":"assets/p282288_b_v2_aa.jpg","size":"Sm","aspect":"3x4","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"720","height":"540","uri":"assets/p282288_b_h6_aa.jpg","size":"Lg","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"960","height":"1440","uri":"assets/p282288_b_v8_aa.jpg","size":"Ms","aspect":"2x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"},{"width":"180","height":"135","uri":"assets/p282288_b_h5_aa.jpg","size":"Sm","aspect":"4x3","category":"Banner-L3","text":"yes","primary":"true","tier":"Series"}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json
new file mode 100644
index 000000000..fe2a94436
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/programs_response.json
@@ -0,0 +1 @@
+[{"programID":"EP000000060003","titles":[{"title120":"'Allo 'Allo!"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"A disguised British Intelligence officer is sent to help the airmen."}]},"originalAirDate":"1985-11-04","genres":["Sitcom"],"episodeTitle150":"The Poloceman Cometh","metadata":[{"Gracenote":{"season":2,"episode":3}}],"cast":[{"personId":"383774","nameId":"392649","name":"Gorden Kaye","role":"Actor","billingOrder":"01"},{"personId":"246840","nameId":"250387","name":"Carmen Silvera","role":"Actor","billingOrder":"02"},{"personId":"376955","nameId":"385830","name":"Rose Hill","role":"Actor","billingOrder":"03"},{"personId":"259773","nameId":"263340","name":"Vicki Michelle","role":"Actor","billingOrder":"04"},{"personId":"353113","nameId":"361987","name":"Kirsten Cooke","role":"Actor","billingOrder":"05"},{"personId":"77787","nameId":"77787","name":"Richard Marner","role":"Actor","billingOrder":"06"},{"personId":"230921","nameId":"234193","name":"Guy Siner","role":"Actor","billingOrder":"07"},{"personId":"374934","nameId":"383809","name":"Kim Hartman","role":"Actor","billingOrder":"08"},{"personId":"369151","nameId":"378026","name":"Richard Gibson","role":"Actor","billingOrder":"09"},{"personId":"343690","nameId":"352564","name":"Arthur Bostrom","role":"Actor","billingOrder":"10"},{"personId":"352557","nameId":"361431","name":"John D. Collins","role":"Actor","billingOrder":"11"},{"personId":"605275","nameId":"627734","name":"Nicholas Frankau","role":"Actor","billingOrder":"12"},{"personId":"373394","nameId":"382269","name":"Jack Haig","role":"Actor","billingOrder":"13"}],"crew":[{"personId":"354407","nameId":"363281","name":"David Croft","role":"Director","billingOrder":"01"},{"personId":"354407","nameId":"363281","name":"David Croft","role":"Writer","billingOrder":"02"},{"personId":"105145","nameId":"105145","name":"Jeremy Lloyd","role":"Writer","billingOrder":"03"}],"showType":"Series","hasImageArtwork":true,"md5":"Jo5NKxoo44xRvBCAq8QT2A"},{"programID":"EP000000510142","titles":[{"title120":"A Different World"}],"eventDetails":{"subType":"Series"},"descriptions":{"description1000":[{"descriptionLanguage":"en","description":"Whitley and Dwayne tell new students about their honeymoon in Los Angeles."}]},"originalAirDate":"1992-09-24","genres":["Sitcom"],"episodeTitle150":"Honeymoon in L.A.","metadata":[{"Gracenote":{"season":6,"episode":1}}],"cast":[{"personId":"700","nameId":"700","name":"Jasmine Guy","role":"Actor","billingOrder":"01"},{"personId":"729","nameId":"729","name":"Kadeem Hardison","role":"Actor","billingOrder":"02"},{"personId":"120","nameId":"120","name":"Darryl M. Bell","role":"Actor","billingOrder":"03"},{"personId":"1729","nameId":"1729","name":"Cree Summer","role":"Actor","billingOrder":"04"},{"personId":"217","nameId":"217","name":"Charnele Brown","role":"Actor","billingOrder":"05"},{"personId":"1811","nameId":"1811","name":"Glynn Turman","role":"Actor","billingOrder":"06"},{"personId":"1232","nameId":"1232","name":"Lou Myers","role":"Actor","billingOrder":"07"},{"personId":"1363","nameId":"1363","name":"Jada Pinkett","role":"Guest Star","billingOrder":"08"},{"personId":"222967","nameId":"225536","name":"Ajai Sanders","role":"Guest Star","billingOrder":"09"},{"personId":"181744","nameId":"183292","name":"Karen Malina White","role":"Guest Star","billingOrder":"10"},{"personId":"305017","nameId":"318897","name":"Patrick Y. Malone","role":"Guest Star","billingOrder":"11"},{"personId":"9841","nameId":"9841","name":"Bumper Robinson","role":"Guest Star","billingOrder":"12"},{"personId":"426422","nameId":"435297","name":"Sister Souljah","role":"Guest Star","billingOrder":"13"},{"personId":"25","nameId":"25","name":"Debbie Allen","role":"Guest Star","billingOrder":"14"},{"personId":"668","nameId":"668","name":"Gilbert Gottfried","role":"Guest Star","billingOrder":"15"}],"showType":"Series","hasImageArtwork":true,"md5":"P5kz0QmCeYxIA+yL0H4DWw"}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json
new file mode 100644
index 000000000..5ef1bfb1c
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_request.json
@@ -0,0 +1 @@
+[{"stationID":"20454","date":["2015-03-13","2015-03-17"]},{"stationID":"10021","date":["2015-03-12","2015-03-13"]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json
new file mode 100644
index 000000000..4a97e5517
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/schedules_response.json
@@ -0,0 +1 @@
+[{"stationID":"20454","programs":[{"programID":"SH005371070000","airDateTime":"2015-03-03T00:00:00Z","duration":1800,"md5":"Sy8HEMBPcuiAx3FBukUhKQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]},{"programID":"EP000014577244","airDateTime":"2015-03-03T00:30:00Z","duration":1800,"md5":"25DNXVXO192JI7Y9vSW9lQ","new":true,"audioProperties":["stereo","cc"],"videoProperties":["hdtv"]}]}]
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json
new file mode 100644
index 000000000..e5fb64a6f
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_live_response.json
@@ -0,0 +1 @@
+{"code":0,"message":"OK","serverID":"AWS-SD-web.1","datetime":"2016-08-23T13:55:25Z","token":"f3fca79989cafe7dead71beefedc812b"}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json
new file mode 100644
index 000000000..b66a4ed0c
--- /dev/null
+++ b/tests/Jellyfin.Server.Implementations.Tests/Test Data/SchedulesDirect/token_offline_response.json
@@ -0,0 +1 @@
+{"response":"SERVICE_OFFLINE","code":3000,"serverID":"20141201.web.1","message":"Server offline for maintenance.","datetime":"2015-04-23T00:03:32Z","token":"CAFEDEADBEEFCAFEDEADBEEFCAFEDEADBEEFCAFE"}
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 38687ae61..9d7b447ed 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,8 +9,8 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.1.21452.15" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index db24df240..67ae0e080 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,8 +10,8 @@
<PackageReference Include="AutoFixture" Version="4.17.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.17.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.17.0" />
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.1.21452.15" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.1.21451.13" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.0-rc.2*" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0-rc.2*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />