aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.vscode/extensions.json14
-rw-r--r--CONTRIBUTORS.md1
-rw-r--r--Dockerfile28
-rw-r--r--DvdLib/BigEndianBinaryReader.cs13
-rw-r--r--DvdLib/DvdLib.csproj4
-rw-r--r--DvdLib/Ifo/Dvd.cs10
-rw-r--r--Emby.Dlna/Didl/DidlBuilder.cs51
-rw-r--r--Emby.Naming/Common/NamingOptions.cs3
-rw-r--r--Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs38
-rw-r--r--Emby.Server.Implementations/Activity/ActivityManager.cs11
-rw-r--r--Emby.Server.Implementations/Activity/ActivityRepository.cs104
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs15
-rw-r--r--Emby.Server.Implementations/Archiving/ZipClient.cs3
-rw-r--r--Emby.Server.Implementations/Channels/ChannelImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs168
-rw-r--r--Emby.Server.Implementations/Channels/ChannelPostScanTask.cs4
-rw-r--r--Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs8
-rw-r--r--Emby.Server.Implementations/Collections/CollectionImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs20
-rw-r--r--Emby.Server.Implementations/Data/SqliteExtensions.cs27
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj2
-rw-r--r--Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs93
-rw-r--r--Emby.Server.Implementations/HttpServer/HttpResultFactory.cs14
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserManager.cs9
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs25
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs24
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/da.json16
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-AR.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/fr.json26
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json5
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json66
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json2
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs5
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs2
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs5
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemsService.cs4
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs6
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs2
-rw-r--r--MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs22
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs4
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs5
-rw-r--r--MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs2
-rw-r--r--MediaBrowser.Model/Net/MimeTypes.cs3
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs6
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs45
-rw-r--r--MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs13
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs4
-rw-r--r--README.md96
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs6
54 files changed, 627 insertions, 447 deletions
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..59d9452fe
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,14 @@
+{
+ // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
+ // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
+
+ // List of extensions which should be recommended for users of this workspace.
+ "recommendations": [
+ "ms-dotnettools.csharp",
+ "editorconfig.editorconfig"
+ ],
+ // List of extensions recommended by VS Code that should not be recommended for users of this workspace.
+ "unwantedRecommendations": [
+
+ ]
+}
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 3d6023b82..ce956176e 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -22,6 +22,7 @@
- [cvium](https://github.com/cvium)
- [dannymichel](https://github.com/dannymichel)
- [DaveChild](https://github.com/DaveChild)
+ - [Delgan](https://github.com/Delgan)
- [dcrdev](https://github.com/dcrdev)
- [dhartung](https://github.com/dhartung)
- [dinki](https://github.com/dinki)
diff --git a/Dockerfile b/Dockerfile
index caac7500a..6e834d4e0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,4 @@
ARG DOTNET_VERSION=3.1
-ARG FFMPEG_VERSION=latest
FROM node:alpine as web-builder
ARG JELLYFIN_WEB_VERSION=master
@@ -17,7 +16,6 @@ ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
# see https://success.docker.com/article/how-to-reserve-resource-temporarily-unavailable-errors-due-to-tasksmax-setting
RUN dotnet publish Jellyfin.Server --disable-parallel --configuration Release --output="/jellyfin" --self-contained --runtime linux-x64 "-p:GenerateDocumentationFile=false;DebugSymbols=false;DebugType=none"
-FROM jellyfin/ffmpeg:${FFMPEG_VERSION} as ffmpeg
FROM debian:buster-slim
# https://askubuntu.com/questions/972516/debian-frontend-environment-variable
@@ -27,32 +25,26 @@ ARG APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=DontWarn
# https://github.com/NVIDIA/nvidia-docker/wiki/Installation-(Native-GPU-Support)
ENV NVIDIA_DRIVER_CAPABILITIES="compute,video,utility"
-COPY --from=ffmpeg /opt/ffmpeg /opt/ffmpeg
COPY --from=builder /jellyfin /jellyfin
COPY --from=web-builder /dist /jellyfin/jellyfin-web
# Install dependencies:
-# libfontconfig1: needed for Skia
-# libgomp1: needed for ffmpeg
-# libva-drm2: needed for ffmpeg
-# mesa-va-drivers: needed for VAAPI
+# mesa-va-drivers: needed for AMD VAAPI
RUN apt-get update \
+ && apt-get install --no-install-recommends --no-install-suggests -y ca-certificates gnupg wget apt-transport-https \
+ && wget -O - https://repo.jellyfin.org/jellyfin_team.gpg.key | apt-key add - \
+ && echo "deb [arch=$( dpkg --print-architecture )] https://repo.jellyfin.org/$( awk -F'=' '/^ID=/{ print $NF }' /etc/os-release ) $( awk -F'=' '/^VERSION_CODENAME=/{ print $NF }' /etc/os-release ) main" | tee /etc/apt/sources.list.d/jellyfin.list \
+ && apt-get update \
&& apt-get install --no-install-recommends --no-install-suggests -y \
- libfontconfig1 \
- libgomp1 \
- libva-drm2 \
mesa-va-drivers \
+ jellyfin-ffmpeg \
openssl \
- ca-certificates \
- vainfo \
- i965-va-driver \
locales \
- && apt-get clean autoclean -y\
- && apt-get autoremove -y\
+ && apt-get remove gnupg wget apt-transport-https -y \
+ && apt-get clean autoclean -y \
+ && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /cache /config /media \
&& chmod 777 /cache /config /media \
- && ln -s /opt/ffmpeg/bin/ffmpeg /usr/local/bin \
- && ln -s /opt/ffmpeg/bin/ffprobe /usr/local/bin \
&& sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
@@ -65,4 +57,4 @@ VOLUME /cache /config /media
ENTRYPOINT ["./jellyfin/jellyfin", \
"--datadir", "/config", \
"--cachedir", "/cache", \
- "--ffmpeg", "/usr/local/bin/ffmpeg"]
+ "--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
diff --git a/DvdLib/BigEndianBinaryReader.cs b/DvdLib/BigEndianBinaryReader.cs
index b3b2eabd5..473005b55 100644
--- a/DvdLib/BigEndianBinaryReader.cs
+++ b/DvdLib/BigEndianBinaryReader.cs
@@ -1,4 +1,4 @@
-using System;
+using System.Buffers.Binary;
using System.IO;
namespace DvdLib
@@ -12,19 +12,12 @@ namespace DvdLib
public override ushort ReadUInt16()
{
- return BitConverter.ToUInt16(ReadAndReverseBytes(2), 0);
+ return BinaryPrimitives.ReadUInt16BigEndian(base.ReadBytes(2));
}
public override uint ReadUInt32()
{
- return BitConverter.ToUInt32(ReadAndReverseBytes(4), 0);
- }
-
- private byte[] ReadAndReverseBytes(int count)
- {
- byte[] val = base.ReadBytes(count);
- Array.Reverse(val, 0, count);
- return val;
+ return BinaryPrimitives.ReadUInt32BigEndian(base.ReadBytes(4));
}
}
}
diff --git a/DvdLib/DvdLib.csproj b/DvdLib/DvdLib.csproj
index f4df6a9f5..36f949616 100644
--- a/DvdLib/DvdLib.csproj
+++ b/DvdLib/DvdLib.csproj
@@ -4,10 +4,6 @@
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
- </ItemGroup>
-
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
diff --git a/DvdLib/Ifo/Dvd.cs b/DvdLib/Ifo/Dvd.cs
index c0f9cf410..5af58a2dc 100644
--- a/DvdLib/Ifo/Dvd.cs
+++ b/DvdLib/Ifo/Dvd.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using MediaBrowser.Model.IO;
namespace DvdLib.Ifo
{
@@ -13,13 +12,10 @@ namespace DvdLib.Ifo
private ushort _titleCount;
public readonly Dictionary<ushort, string> VTSPaths = new Dictionary<ushort, string>();
- private readonly IFileSystem _fileSystem;
-
- public Dvd(string path, IFileSystem fileSystem)
+ public Dvd(string path)
{
- _fileSystem = fileSystem;
Titles = new List<Title>();
- var allFiles = _fileSystem.GetFiles(path, true).ToList();
+ var allFiles = new DirectoryInfo(path).GetFiles(path, SearchOption.AllDirectories);
var vmgPath = allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.IFO", StringComparison.OrdinalIgnoreCase)) ??
allFiles.FirstOrDefault(i => string.Equals(i.Name, "VIDEO_TS.BUP", StringComparison.OrdinalIgnoreCase));
@@ -76,7 +72,7 @@ namespace DvdLib.Ifo
}
}
- private void ReadVTS(ushort vtsNum, IEnumerable<FileSystemMetadata> allFiles)
+ private void ReadVTS(ushort vtsNum, IReadOnlyList<FileInfo> allFiles)
{
var filename = string.Format("VTS_{0:00}_0.IFO", vtsNum);
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 88cc00e30..59951f6d9 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -1018,19 +1018,58 @@ namespace Emby.Dlna.Didl
}
}
- item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
+ // For audio tracks without art use album art if available.
+ if (item is Audio audioItem)
+ {
+ var album = audioItem.AlbumEntity;
+ return album != null && album.HasImage(ImageType.Primary)
+ ? GetImageInfo(album, ImageType.Primary)
+ : null;
+ }
- if (item != null)
+ // Don't look beyond album/playlist level. Metadata service may assign an image from a different album/show to the parent folder.
+ if (item is MusicAlbum || item is Playlist)
{
- if (item.HasImage(ImageType.Primary))
- {
- return GetImageInfo(item, ImageType.Primary);
- }
+ return null;
+ }
+
+ // For other item types check parents, but be aware that image retrieved from a parent may be not suitable for this media item.
+ var parentWithImage = GetFirstParentWithImageBelowUserRoot(item);
+ if (parentWithImage != null)
+ {
+ return GetImageInfo(parentWithImage, ImageType.Primary);
}
return null;
}
+ private BaseItem GetFirstParentWithImageBelowUserRoot(BaseItem item)
+ {
+ if (item == null)
+ {
+ return null;
+ }
+
+ if (item.HasImage(ImageType.Primary))
+ {
+ return item;
+ }
+
+ var parent = item.GetParent();
+ if (parent is UserRootFolder)
+ {
+ return null;
+ }
+
+ // terminate in case we went past user root folder (unlikely?)
+ if (parent is Folder folder && folder.IsRoot)
+ {
+ return null;
+ }
+
+ return GetFirstParentWithImageBelowUserRoot(parent);
+ }
+
private ImageDownloadInfo GetImageInfo(BaseItem item, ImageType type)
{
var imageInfo = item.GetImageInfo(type, 0);
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index c00181c7e..a2d75d0b8 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -136,7 +136,8 @@ namespace Emby.Naming.Common
CleanDateTimes = new[]
{
- @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
+ @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
+ @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
};
CleanStrings = new[]
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 13733b60f..0925d72a6 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -27,6 +25,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
+ /// <summary>
+ /// Entry point for the activity logger.
+ /// </summary>
public sealed class ActivityLogEntryPoint : IServerEntryPoint
{
private readonly ILogger _logger;
@@ -42,16 +43,15 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
/// </summary>
- /// <param name="logger"></param>
- /// <param name="sessionManager"></param>
- /// <param name="deviceManager"></param>
- /// <param name="taskManager"></param>
- /// <param name="activityManager"></param>
- /// <param name="localization"></param>
- /// <param name="installationManager"></param>
- /// <param name="subManager"></param>
- /// <param name="userManager"></param>
- /// <param name="appHost"></param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="sessionManager">The session manager.</param>
+ /// <param name="deviceManager">The device manager.</param>
+ /// <param name="taskManager">The task manager.</param>
+ /// <param name="activityManager">The activity manager.</param>
+ /// <param name="localization">The localization manager.</param>
+ /// <param name="installationManager">The installation manager.</param>
+ /// <param name="subManager">The subtitle manager.</param>
+ /// <param name="userManager">The user manager.</param>
public ActivityLogEntryPoint(
ILogger<ActivityLogEntryPoint> logger,
ISessionManager sessionManager,
@@ -74,6 +74,7 @@ namespace Emby.Server.Implementations.Activity
_userManager = userManager;
}
+ /// <inheritdoc />
public Task RunAsync()
{
_taskManager.TaskCompleted += OnTaskCompleted;
@@ -168,7 +169,12 @@ namespace Emby.Server.Implementations.Activity
CreateLogEntry(new ActivityLogEntry
{
- Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserStoppedPlayingItemWithValues"),
+ user.Name,
+ GetItemName(item),
+ e.DeviceName),
Type = GetPlaybackStoppedNotificationType(item.MediaType),
UserId = user.Id
});
@@ -485,8 +491,8 @@ namespace Emby.Server.Implementations.Activity
var result = e.Result;
var task = e.Task;
- var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
- if (activityTask != null && !activityTask.IsLogged)
+ if (task.ScheduledTask is IConfigurableScheduledTask activityTask
+ && !activityTask.IsLogged)
{
return;
}
@@ -560,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
/// <summary>
/// Constructs a user-friendly string for this TimeSpan instance.
/// </summary>
- public static string ToUserFriendlyString(TimeSpan span)
+ private static string ToUserFriendlyString(TimeSpan span)
{
const int DaysInYear = 365;
const int DaysInMonth = 30;
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index ee10845cf..c1d8dd8da 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -11,22 +11,17 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityManager : IActivityManager
{
- public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
-
private readonly IActivityRepository _repo;
- private readonly ILogger _logger;
private readonly IUserManager _userManager;
- public ActivityManager(
- ILoggerFactory loggerFactory,
- IActivityRepository repo,
- IUserManager userManager)
+ public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
- _logger = loggerFactory.CreateLogger(nameof(ActivityManager));
_repo = repo;
_userManager = userManager;
}
+ public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
+
public void Create(ActivityLogEntry entry)
{
entry.Date = DateTime.UtcNow;
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index 7be72319e..26fc229f7 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -17,11 +17,12 @@ namespace Emby.Server.Implementations.Activity
{
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
+ private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
+
private readonly IFileSystem _fileSystem;
- public ActivityRepository(ILoggerFactory loggerFactory, IServerApplicationPaths appPaths, IFileSystem fileSystem)
- : base(loggerFactory.CreateLogger(nameof(ActivityRepository)))
+ public ActivityRepository(ILogger<ActivityRepository> logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
+ : base(logger)
{
DbFilePath = Path.Combine(appPaths.DataPath, "activitylog.db");
_fileSystem = fileSystem;
@@ -76,8 +77,6 @@ namespace Emby.Server.Implementations.Activity
}
}
- private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
-
public void Create(ActivityLogEntry entry)
{
if (entry == null)
@@ -87,32 +86,34 @@ namespace Emby.Server.Implementations.Activity
using (var connection = GetConnection())
{
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
+ connection.RunInTransaction(
+ db =>
{
- statement.TryBind("@Name", entry.Name);
+ using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
+ {
+ statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- }, TransactionMode);
+ statement.MoveNext();
+ }
+ },
+ TransactionMode);
}
}
@@ -125,33 +126,35 @@ namespace Emby.Server.Implementations.Activity
using (var connection = GetConnection())
{
- connection.RunInTransaction(db =>
- {
- using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
+ connection.RunInTransaction(
+ db =>
{
- statement.TryBind("@Id", entry.Id);
+ using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
+ {
+ statement.TryBind("@Id", entry.Id);
- statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Name", entry.Name);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- }, TransactionMode);
+ statement.MoveNext();
+ }
+ },
+ TransactionMode);
}
}
@@ -164,6 +167,7 @@ namespace Emby.Server.Implementations.Activity
{
whereClauses.Add("DateCreated>=@DateCreated");
}
+
if (hasUserId.HasValue)
{
if (hasUserId.Value)
@@ -204,7 +208,7 @@ namespace Emby.Server.Implementations.Activity
if (limit.HasValue)
{
- commandText += " LIMIT " + limit.Value.ToString(_usCulture);
+ commandText += " LIMIT " + limit.Value.ToString(CultureInfo.InvariantCulture);
}
var statementTexts = new[]
@@ -304,7 +308,7 @@ namespace Emby.Server.Implementations.Activity
index++;
if (reader[index].SQLiteType != SQLiteType.Null)
{
- info.Severity = (LogLevel)Enum.Parse(typeof(LogLevel), reader[index].ToString(), true);
+ info.Severity = Enum.Parse<LogLevel>(reader[index].ToString(), true);
}
return info;
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8364ca45d..d6d153818 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -768,7 +768,16 @@ namespace Emby.Server.Implementations
DtoService = new DtoService(LoggerFactory, LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ProviderManager, this, () => MediaSourceManager, () => LiveTvManager);
serviceCollection.AddSingleton(DtoService);
- ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, LoggerFactory, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, ProviderManager);
+ ChannelManager = new ChannelManager(
+ UserManager,
+ DtoService,
+ LibraryManager,
+ LoggerFactory.CreateLogger<ChannelManager>(),
+ ServerConfigurationManager,
+ FileSystemManager,
+ UserDataManager,
+ JsonSerializer,
+ ProviderManager);
serviceCollection.AddSingleton(ChannelManager);
SessionManager = new SessionManager(
@@ -820,7 +829,7 @@ namespace Emby.Server.Implementations
var activityLogRepo = GetActivityLogRepository();
serviceCollection.AddSingleton(activityLogRepo);
- serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(LoggerFactory, activityLogRepo, UserManager));
+ serviceCollection.AddSingleton<IActivityManager>(new ActivityManager(activityLogRepo, UserManager));
var authContext = new AuthorizationContext(AuthenticationRepository, UserManager);
serviceCollection.AddSingleton<IAuthorizationContext>(authContext);
@@ -957,7 +966,7 @@ namespace Emby.Server.Implementations
private IActivityRepository GetActivityLogRepository()
{
- var repo = new ActivityRepository(LoggerFactory, ServerConfigurationManager.ApplicationPaths, FileSystemManager);
+ var repo = new ActivityRepository(LoggerFactory.CreateLogger<ActivityRepository>(), ServerConfigurationManager.ApplicationPaths, FileSystemManager);
repo.Initialize();
diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs
index 4a6e5cfd7..e01495e19 100644
--- a/Emby.Server.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Server.Implementations/Archiving/ZipClient.cs
@@ -50,6 +50,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
+ /// <inheritdoc />
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = ZipReader.Open(source))
@@ -66,6 +67,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
+ /// <inheritdoc />
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
using (var reader = GZipReader.Open(source))
@@ -82,6 +84,7 @@ namespace Emby.Server.Implementations.Archiving
}
}
+ /// <inheritdoc />
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{
using (var reader = GZipReader.Open(source))
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index 62aeb9bcb..cf56a5fae 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -20,6 +20,8 @@ namespace Emby.Server.Implementations.Channels
_channelManager = channelManager;
}
+ public string Name => "Channel Image Provider";
+
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
@@ -32,8 +34,6 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken);
}
- public string Name => "Channel Image Provider";
-
public bool Supports(BaseItem item)
{
return item is Channel;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 6e1baddfe..8502af97a 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -31,8 +31,6 @@ namespace Emby.Server.Implementations.Channels
{
public class ChannelManager : IChannelManager
{
- internal IChannel[] Channels { get; private set; }
-
private readonly IUserManager _userManager;
private readonly IUserDataManager _userDataManager;
private readonly IDtoService _dtoService;
@@ -43,11 +41,16 @@ namespace Emby.Server.Implementations.Channels
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
+ private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
+ new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
+
+ private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
+
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
ILibraryManager libraryManager,
- ILoggerFactory loggerFactory,
+ ILogger<ChannelManager> logger,
IServerConfigurationManager config,
IFileSystem fileSystem,
IUserDataManager userDataManager,
@@ -57,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
_userManager = userManager;
_dtoService = dtoService;
_libraryManager = libraryManager;
- _logger = loggerFactory.CreateLogger(nameof(ChannelManager));
+ _logger = logger;
_config = config;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
@@ -65,6 +68,8 @@ namespace Emby.Server.Implementations.Channels
_providerManager = providerManager;
}
+ internal IChannel[] Channels { get; private set; }
+
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
public void AddParts(IEnumerable<IChannel> channels)
@@ -85,8 +90,7 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- var supportsDelete = channel as ISupportsDelete;
- return supportsDelete != null && supportsDelete.CanDelete(item);
+ return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
public bool EnableMediaProbe(BaseItem item)
@@ -146,15 +150,13 @@ namespace Emby.Server.Implementations.Channels
{
try
{
- var hasAttributes = GetChannelProvider(i) as IHasFolderAttributes;
-
- return (hasAttributes != null && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
+ return (GetChannelProvider(i) is IHasFolderAttributes hasAttributes
+ && hasAttributes.Attributes.Contains("Recordings", StringComparer.OrdinalIgnoreCase)) == val;
}
catch
{
return false;
}
-
}).ToList();
}
@@ -171,7 +173,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
@@ -188,9 +189,9 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
+
if (query.IsFavorite.HasValue)
{
var val = query.IsFavorite.Value;
@@ -215,7 +216,6 @@ namespace Emby.Server.Implementations.Channels
{
return false;
}
-
}).ToList();
}
@@ -226,6 +226,7 @@ namespace Emby.Server.Implementations.Channels
{
all = all.Skip(query.StartIndex.Value).ToList();
}
+
if (query.Limit.HasValue)
{
all = all.Take(query.Limit.Value).ToList();
@@ -256,11 +257,9 @@ namespace Emby.Server.Implementations.Channels
var internalResult = GetChannelsInternal(query);
- var dtoOptions = new DtoOptions()
- {
- };
+ var dtoOptions = new DtoOptions();
- //TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
+ // TODO Fix The co-variant conversion (internalResult.Items) between Folder[] and BaseItem[], this can generate runtime issues.
var returnItems = _dtoService.GetBaseItemDtos(internalResult.Items, dtoOptions, user);
var result = new QueryResult<BaseItemDto>
@@ -341,8 +340,8 @@ namespace Emby.Server.Implementations.Channels
}
catch
{
-
}
+
return;
}
@@ -365,11 +364,9 @@ namespace Emby.Server.Implementations.Channels
var channel = GetChannel(item.ChannelId);
var channelPlugin = GetChannelProvider(channel);
- var requiresCallback = channelPlugin as IRequiresMediaInfoCallback;
-
IEnumerable<MediaSourceInfo> results;
- if (requiresCallback != null)
+ if (channelPlugin is IRequiresMediaInfoCallback requiresCallback)
{
results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken)
.ConfigureAwait(false);
@@ -384,9 +381,6 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
- private readonly ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>> _channelItemMediaInfo =
- new ConcurrentDictionary<string, Tuple<DateTime, List<MediaSourceInfo>>>();
-
private async Task<IEnumerable<MediaSourceInfo>> GetChannelItemMediaSourcesInternal(IRequiresMediaInfoCallback channel, string id, CancellationToken cancellationToken)
{
if (_channelItemMediaInfo.TryGetValue(id, out Tuple<DateTime, List<MediaSourceInfo>> cachedInfo))
@@ -444,18 +438,21 @@ namespace Emby.Server.Implementations.Channels
{
isNew = true;
}
+
item.Path = path;
if (!item.ChannelId.Equals(id))
{
forceUpdate = true;
}
+
item.ChannelId = id;
if (item.ParentId != parentFolderId)
{
forceUpdate = true;
}
+
item.ParentId = parentFolderId;
item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
@@ -472,10 +469,12 @@ namespace Emby.Server.Implementations.Channels
_libraryManager.CreateItem(item, null);
}
- await item.RefreshMetadata(new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- ForceSave = !isNew && forceUpdate
- }, cancellationToken).ConfigureAwait(false);
+ await item.RefreshMetadata(
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ ForceSave = !isNew && forceUpdate
+ },
+ cancellationToken).ConfigureAwait(false);
return item;
}
@@ -509,12 +508,12 @@ namespace Emby.Server.Implementations.Channels
public ChannelFeatures[] GetAllChannelFeatures()
{
- return _libraryManager.GetItemIds(new InternalItemsQuery
- {
- IncludeItemTypes = new[] { typeof(Channel).Name },
- OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
-
- }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
+ return _libraryManager.GetItemIds(
+ new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(Channel).Name },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) }
+ }).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
public ChannelFeatures GetChannelFeatures(string id)
@@ -532,13 +531,13 @@ namespace Emby.Server.Implementations.Channels
public bool SupportsExternalTransfer(Guid channelId)
{
- //var channel = GetChannel(channelId);
var channelProvider = GetChannelProvider(channelId);
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
}
- public ChannelFeatures GetChannelFeaturesDto(Channel channel,
+ public ChannelFeatures GetChannelFeaturesDto(
+ Channel channel,
IChannel provider,
InternalChannelFeatures features)
{
@@ -567,6 +566,7 @@ namespace Emby.Server.Implementations.Channels
{
throw new ArgumentNullException(nameof(name));
}
+
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
@@ -614,7 +614,7 @@ namespace Emby.Server.Implementations.Channels
query.IsFolder = false;
// hack for trailers, figure out a better way later
- var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.IndexOf("Trailer") != -1;
+ var sortByPremiereDate = channels.Length == 1 && channels[0].GetType().Name.Contains("Trailer", StringComparison.Ordinal);
if (sortByPremiereDate)
{
@@ -640,10 +640,12 @@ namespace Emby.Server.Implementations.Channels
{
var internalChannel = await GetChannel(channel, cancellationToken).ConfigureAwait(false);
- var query = new InternalItemsQuery();
- query.Parent = internalChannel;
- query.EnableTotalRecordCount = false;
- query.ChannelIds = new Guid[] { internalChannel.Id };
+ var query = new InternalItemsQuery
+ {
+ Parent = internalChannel,
+ EnableTotalRecordCount = false,
+ ChannelIds = new Guid[] { internalChannel.Id }
+ };
var result = await GetChannelItemsInternal(query, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
@@ -651,13 +653,15 @@ namespace Emby.Server.Implementations.Channels
{
if (item is Folder folder)
{
- await GetChannelItemsInternal(new InternalItemsQuery
- {
- Parent = folder,
- EnableTotalRecordCount = false,
- ChannelIds = new Guid[] { internalChannel.Id }
-
- }, new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
+ await GetChannelItemsInternal(
+ new InternalItemsQuery
+ {
+ Parent = folder,
+ EnableTotalRecordCount = false,
+ ChannelIds = new Guid[] { internalChannel.Id }
+ },
+ new SimpleProgress<double>(),
+ cancellationToken).ConfigureAwait(false);
}
}
}
@@ -672,7 +676,8 @@ namespace Emby.Server.Implementations.Channels
var parentItem = query.ParentId == Guid.Empty ? channel : _libraryManager.GetItemById(query.ParentId);
- var itemsResult = await GetChannelItems(channelProvider,
+ var itemsResult = await GetChannelItems(
+ channelProvider,
query.User,
parentItem is Channel ? null : parentItem.ExternalId,
null,
@@ -684,13 +689,12 @@ namespace Emby.Server.Implementations.Channels
{
query.Parent = channel;
}
+
query.ChannelIds = Array.Empty<Guid>();
// Not yet sure why this is causing a problem
query.GroupByPresentationUniqueKey = false;
- //_logger.LogDebug("GetChannelItemsInternal");
-
// null if came from cache
if (itemsResult != null)
{
@@ -707,12 +711,15 @@ namespace Emby.Server.Implementations.Channels
var deadItem = _libraryManager.GetItemById(deadId);
if (deadItem != null)
{
- _libraryManager.DeleteItem(deadItem, new DeleteOptions
- {
- DeleteFileLocation = false,
- DeleteFromExternalProvider = false
-
- }, parentItem, false);
+ _libraryManager.DeleteItem(
+ deadItem,
+ new DeleteOptions
+ {
+ DeleteFileLocation = false,
+ DeleteFromExternalProvider = false
+ },
+ parentItem,
+ false);
}
}
}
@@ -735,7 +742,6 @@ namespace Emby.Server.Implementations.Channels
return result;
}
- private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
private async Task<ChannelItemResult> GetChannelItems(IChannel channel,
User user,
string externalFolderId,
@@ -743,7 +749,7 @@ namespace Emby.Server.Implementations.Channels
bool sortDescending,
CancellationToken cancellationToken)
{
- var userId = user == null ? null : user.Id.ToString("N", CultureInfo.InvariantCulture);
+ var userId = user?.Id.ToString("N", CultureInfo.InvariantCulture);
var cacheLength = CacheLength;
var cachePath = GetChannelDataCachePath(channel, userId, externalFolderId, sortField, sortDescending);
@@ -761,11 +767,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
await _resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
@@ -785,11 +789,9 @@ namespace Emby.Server.Implementations.Channels
}
catch (FileNotFoundException)
{
-
}
catch (IOException)
{
-
}
var query = new InternalChannelItemQuery
@@ -833,7 +835,8 @@ namespace Emby.Server.Implementations.Channels
}
}
- private string GetChannelDataCachePath(IChannel channel,
+ private string GetChannelDataCachePath(
+ IChannel channel,
string userId,
string externalFolderId,
ChannelItemSortField? sortField,
@@ -843,8 +846,7 @@ namespace Emby.Server.Implementations.Channels
var userCacheKey = string.Empty;
- var hasCacheKey = channel as IHasCacheKey;
- if (hasCacheKey != null)
+ if (channel is IHasCacheKey hasCacheKey)
{
userCacheKey = hasCacheKey.GetCacheKey(userId) ?? string.Empty;
}
@@ -858,6 +860,7 @@ namespace Emby.Server.Implementations.Channels
{
filename += "-sortField-" + sortField.Value;
}
+
if (sortDescending)
{
filename += "-sortDescending";
@@ -865,7 +868,8 @@ namespace Emby.Server.Implementations.Channels
filename = filename.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- return Path.Combine(_config.ApplicationPaths.CachePath,
+ return Path.Combine(
+ _config.ApplicationPaths.CachePath,
"channels",
channelId,
version,
@@ -981,7 +985,6 @@ namespace Emby.Server.Implementations.Channels
{
item.RunTimeTicks = null;
}
-
else if (isNew || !enableMediaProbe)
{
item.RunTimeTicks = info.RunTimeTicks;
@@ -1014,26 +1017,24 @@ namespace Emby.Server.Implementations.Channels
}
}
- var hasArtists = item as IHasArtist;
- if (hasArtists != null)
+ if (item is IHasArtist hasArtists)
{
hasArtists.Artists = info.Artists.ToArray();
}
- var hasAlbumArtists = item as IHasAlbumArtist;
- if (hasAlbumArtists != null)
+ if (item is IHasAlbumArtist hasAlbumArtists)
{
hasAlbumArtists.AlbumArtists = info.AlbumArtists.ToArray();
}
- var trailer = item as Trailer;
- if (trailer != null)
+ if (item is Trailer trailer)
{
if (!info.TrailerTypes.SequenceEqual(trailer.TrailerTypes))
{
_logger.LogDebug("Forcing update due to TrailerTypes {0}", item.Name);
forceUpdate = true;
}
+
trailer.TrailerTypes = info.TrailerTypes.ToArray();
}
@@ -1057,6 +1058,7 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ChannelId {0}", item.Name);
}
+
item.ChannelId = internalChannelId;
if (!item.ParentId.Equals(parentFolderId))
@@ -1064,16 +1066,17 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to parent folder Id {0}", item.Name);
}
+
item.ParentId = parentFolderId;
- var hasSeries = item as IHasSeries;
- if (hasSeries != null)
+ if (item is IHasSeries hasSeries)
{
if (!string.Equals(hasSeries.SeriesName, info.SeriesName, StringComparison.OrdinalIgnoreCase))
{
forceUpdate = true;
_logger.LogDebug("Forcing update due to SeriesName {0}", item.Name);
}
+
hasSeries.SeriesName = info.SeriesName;
}
@@ -1082,24 +1085,23 @@ namespace Emby.Server.Implementations.Channels
forceUpdate = true;
_logger.LogDebug("Forcing update due to ExternalId {0}", item.Name);
}
+
item.ExternalId = info.Id;
- var channelAudioItem = item as Audio;
- if (channelAudioItem != null)
+ if (item is Audio channelAudioItem)
{
channelAudioItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
- item.Path = mediaSource == null ? null : mediaSource.Path;
+ item.Path = mediaSource?.Path;
}
- var channelVideoItem = item as Video;
- if (channelVideoItem != null)
+ if (item is Video channelVideoItem)
{
channelVideoItem.ExtraType = info.ExtraType;
var mediaSource = info.MediaSources.FirstOrDefault();
- item.Path = mediaSource == null ? null : mediaSource.Path;
+ item.Path = mediaSource?.Path;
}
if (!string.IsNullOrEmpty(info.ImageUrl) && !item.HasImage(ImageType.Primary))
@@ -1156,7 +1158,7 @@ namespace Emby.Server.Implementations.Channels
}
}
- if (isNew || forceUpdate || item.DateLastRefreshed == default(DateTime))
+ if (isNew || forceUpdate || item.DateLastRefreshed == default)
{
_providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.Normal);
}
diff --git a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
index 266d539d0..250e9da64 100644
--- a/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
+++ b/Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
@@ -14,14 +14,12 @@ namespace Emby.Server.Implementations.Channels
public class ChannelPostScanTask
{
private readonly IChannelManager _channelManager;
- private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
- public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
+ public ChannelPostScanTask(IChannelManager channelManager, ILogger logger, ILibraryManager libraryManager)
{
_channelManager = channelManager;
- _userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
}
diff --git a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
index 367efcb13..e1d35f2b0 100644
--- a/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
+++ b/Emby.Server.Implementations/Channels/RefreshChannelsScheduledTask.cs
@@ -7,29 +7,26 @@ using System.Threading.Tasks;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Globalization;
namespace Emby.Server.Implementations.Channels
{
public class RefreshChannelsScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly IChannelManager _channelManager;
- private readonly IUserManager _userManager;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localization;
public RefreshChannelsScheduledTask(
IChannelManager channelManager,
- IUserManager userManager,
ILogger<RefreshChannelsScheduledTask> logger,
ILibraryManager libraryManager,
ILocalizationManager localization)
{
_channelManager = channelManager;
- _userManager = userManager;
_logger = logger;
_libraryManager = libraryManager;
_localization = localization;
@@ -63,7 +60,7 @@ namespace Emby.Server.Implementations.Channels
await manager.RefreshChannels(new SimpleProgress<double>(), cancellationToken).ConfigureAwait(false);
- await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
+ await new ChannelPostScanTask(_channelManager, _logger, _libraryManager).Run(progress, cancellationToken)
.ConfigureAwait(false);
}
@@ -72,7 +69,6 @@ namespace Emby.Server.Implementations.Channels
{
return new[]
{
-
// Every so often
new TaskTriggerInfo
{
diff --git a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
index 21ba0288e..320552465 100644
--- a/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
+++ b/Emby.Server.Implementations/Collections/CollectionImageProvider.cs
@@ -46,9 +46,7 @@ namespace Emby.Server.Implementations.Collections
{
var subItem = i;
- var episode = subItem as Episode;
-
- if (episode != null)
+ if (subItem is Episode episode)
{
var series = episode.Series;
if (series != null && series.HasImage(ImageType.Primary))
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 321952874..68bcc5a4f 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -52,7 +52,9 @@ namespace Emby.Server.Implementations.Collections
}
public event EventHandler<CollectionCreatedEventArgs> CollectionCreated;
+
public event EventHandler<CollectionModifiedEventArgs> ItemsAddedToCollection;
+
public event EventHandler<CollectionModifiedEventArgs> ItemsRemovedFromCollection;
private IEnumerable<Folder> FindFolders(string path)
@@ -109,9 +111,9 @@ namespace Emby.Server.Implementations.Collections
{
var folder = GetCollectionsFolder(false).Result;
- return folder == null ?
- new List<BoxSet>() :
- folder.GetChildren(user, true).OfType<BoxSet>();
+ return folder == null
+ ? Enumerable.Empty<BoxSet>()
+ : folder.GetChildren(user, true).OfType<BoxSet>();
}
public BoxSet CreateCollection(CollectionCreationOptions options)
@@ -191,7 +193,6 @@ namespace Emby.Server.Implementations.Collections
private void AddToCollection(Guid collectionId, IEnumerable<string> ids, bool fireEvent, MetadataRefreshOptions refreshOptions)
{
var collection = _libraryManager.GetItemById(collectionId) as BoxSet;
-
if (collection == null)
{
throw new ArgumentException("No collection exists with the supplied Id");
@@ -289,10 +290,13 @@ namespace Emby.Server.Implementations.Collections
}
collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None);
- _providerManager.QueueRefresh(collection.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- ForceSave = true
- }, RefreshPriority.High);
+ _providerManager.QueueRefresh(
+ collection.Id,
+ new MetadataRefreshOptions(new DirectoryService(_fileSystem))
+ {
+ ForceSave = true
+ },
+ RefreshPriority.High);
ItemsRemovedFromCollection?.Invoke(this, new CollectionModifiedEventArgs
{
diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs
index e7c5270b9..716e5071d 100644
--- a/Emby.Server.Implementations/Data/SqliteExtensions.cs
+++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs
@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
-using MediaBrowser.Model.Serialization;
using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Data
@@ -109,25 +107,6 @@ namespace Emby.Server.Implementations.Data
return null;
}
- /// <summary>
- /// Serializes to bytes.
- /// </summary>
- /// <returns>System.Byte[][].</returns>
- /// <exception cref="ArgumentNullException">obj</exception>
- public static byte[] SerializeToBytes(this IJsonSerializer json, object obj)
- {
- if (obj == null)
- {
- throw new ArgumentNullException(nameof(obj));
- }
-
- using (var stream = new MemoryStream())
- {
- json.SerializeToStream(obj, stream);
- return stream.ToArray();
- }
- }
-
public static void Attach(SQLiteDatabaseConnection db, string path, string alias)
{
var commandText = string.Format(
@@ -383,11 +362,11 @@ namespace Emby.Server.Implementations.Data
}
}
- public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement This)
+ public static IEnumerable<IReadOnlyList<IResultSetValue>> ExecuteQuery(this IStatement statement)
{
- while (This.MoveNext())
+ while (statement.MoveNext())
{
- yield return This.Current;
+ yield return statement.Current;
}
}
}
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index d46b9507b..3d065f70a 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -33,7 +33,7 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
- <PackageReference Include="Mono.Nat" Version="2.0.0" />
+ <PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
<PackageReference Include="sharpcompress" Version="0.25.0" />
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
diff --git a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index e290c62e1..37d7fd479 100644
--- a/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/Emby.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -1,6 +1,7 @@
#pragma warning disable CS1591
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net;
using System.Text;
@@ -26,10 +27,10 @@ namespace Emby.Server.Implementations.EntryPoints
private readonly IServerConfigurationManager _config;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly object _createdRulesLock = new object();
- private List<IPEndPoint> _createdRules = new List<IPEndPoint>();
+ private readonly ConcurrentDictionary<IPEndPoint, byte> _createdRules = new ConcurrentDictionary<IPEndPoint, byte>();
+
private Timer _timer;
- private string _lastConfigIdentifier;
+ private string _configIdentifier;
private bool _disposed = false;
@@ -60,6 +61,7 @@ namespace Emby.Server.Implementations.EntryPoints
return new StringBuilder(32)
.Append(config.EnableUPnP).Append(Separator)
.Append(config.PublicPort).Append(Separator)
+ .Append(config.PublicHttpsPort).Append(Separator)
.Append(_appHost.HttpPort).Append(Separator)
.Append(_appHost.HttpsPort).Append(Separator)
.Append(_appHost.EnableHttps).Append(Separator)
@@ -69,7 +71,10 @@ namespace Emby.Server.Implementations.EntryPoints
private void OnConfigurationUpdated(object sender, EventArgs e)
{
- if (!string.Equals(_lastConfigIdentifier, GetConfigIdentifier(), StringComparison.OrdinalIgnoreCase))
+ var oldConfigIdentifier = _configIdentifier;
+ _configIdentifier = GetConfigIdentifier();
+
+ if (!string.Equals(_configIdentifier, oldConfigIdentifier, StringComparison.OrdinalIgnoreCase))
{
Stop();
Start();
@@ -93,21 +98,19 @@ namespace Emby.Server.Implementations.EntryPoints
return;
}
- _logger.LogDebug("Starting NAT discovery");
+ _logger.LogInformation("Starting NAT discovery");
NatUtility.DeviceFound += OnNatUtilityDeviceFound;
NatUtility.StartDiscovery();
- _timer = new Timer(ClearCreatedRules, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
+ _timer = new Timer((_) => _createdRules.Clear(), null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10));
_deviceDiscovery.DeviceDiscovered += OnDeviceDiscoveryDeviceDiscovered;
-
- _lastConfigIdentifier = GetConfigIdentifier();
}
private void Stop()
{
- _logger.LogDebug("Stopping NAT discovery");
+ _logger.LogInformation("Stopping NAT discovery");
NatUtility.StopDiscovery();
NatUtility.DeviceFound -= OnNatUtilityDeviceFound;
@@ -117,26 +120,16 @@ namespace Emby.Server.Implementations.EntryPoints
_deviceDiscovery.DeviceDiscovered -= OnDeviceDiscoveryDeviceDiscovered;
}
- private void ClearCreatedRules(object state)
- {
- lock (_createdRulesLock)
- {
- _createdRules.Clear();
- }
- }
-
private void OnDeviceDiscoveryDeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
NatUtility.Search(e.Argument.LocalIpAddress, NatProtocol.Upnp);
}
- private void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
+ private async void OnNatUtilityDeviceFound(object sender, DeviceEventArgs e)
{
try
{
- var device = e.Device;
-
- CreateRules(device);
+ await CreateRules(e.Device).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -144,7 +137,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- private async void CreateRules(INatDevice device)
+ private Task CreateRules(INatDevice device)
{
if (_disposed)
{
@@ -153,50 +146,46 @@ namespace Emby.Server.Implementations.EntryPoints
// On some systems the device discovered event seems to fire repeatedly
// This check will help ensure we're not trying to port map the same device over and over
- var address = device.DeviceEndpoint;
-
- lock (_createdRulesLock)
+ if (!_createdRules.TryAdd(device.DeviceEndpoint, 0))
{
- if (!_createdRules.Contains(address))
- {
- _createdRules.Add(address);
- }
- else
- {
- return;
- }
+ return Task.CompletedTask;
}
- try
- {
- await CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error creating http port map");
- return;
- }
+ return Task.WhenAll(CreatePortMaps(device));
+ }
- try
- {
- await CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort).ConfigureAwait(false);
- }
- catch (Exception ex)
+ private IEnumerable<Task> CreatePortMaps(INatDevice device)
+ {
+ yield return CreatePortMap(device, _appHost.HttpPort, _config.Configuration.PublicPort);
+
+ if (_appHost.EnableHttps)
{
- _logger.LogError(ex, "Error creating https port map");
+ yield return CreatePortMap(device, _appHost.HttpsPort, _config.Configuration.PublicHttpsPort);
}
}
- private Task<Mapping> CreatePortMap(INatDevice device, int privatePort, int publicPort)
+ private async Task CreatePortMap(INatDevice device, int privatePort, int publicPort)
{
_logger.LogDebug(
- "Creating port map on local port {0} to public port {1} with device {2}",
+ "Creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}",
privatePort,
publicPort,
device.DeviceEndpoint);
- return device.CreatePortMapAsync(
- new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name));
+ try
+ {
+ var mapping = new Mapping(Protocol.Tcp, privatePort, publicPort, 0, _appHost.Name);
+ await device.CreatePortMapAsync(mapping).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(
+ ex,
+ "Error creating port map on local port {LocalPort} to public port {PublicPort} with device {DeviceEndpoint}.",
+ privatePort,
+ publicPort,
+ device.DeviceEndpoint);
+ }
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
index b42662420..464ca3a0b 100644
--- a/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
+++ b/Emby.Server.Implementations/HttpServer/HttpResultFactory.cs
@@ -28,6 +28,12 @@ namespace Emby.Server.Implementations.HttpServer
/// </summary>
public class HttpResultFactory : IHttpResultFactory
{
+ // Last-Modified and If-Modified-Since must follow strict date format,
+ // see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Modified-Since
+ private const string HttpDateFormat = "ddd, dd MMM yyyy HH:mm:ss \"GMT\"";
+ // We specifically use en-US culture because both day of week and month names require it
+ private static readonly CultureInfo _enUSculture = new CultureInfo("en-US", false);
+
/// <summary>
/// The logger.
/// </summary>
@@ -420,7 +426,11 @@ namespace Emby.Server.Implementations.HttpServer
if (!noCache)
{
- DateTime.TryParse(requestContext.Headers[HeaderNames.IfModifiedSince], out var ifModifiedSinceHeader);
+ if (!DateTime.TryParseExact(requestContext.Headers[HeaderNames.IfModifiedSince], HttpDateFormat, _enUSculture, DateTimeStyles.AssumeUniversal, out var ifModifiedSinceHeader))
+ {
+ _logger.LogDebug("Failed to parse If-Modified-Since header date: {0}", requestContext.Headers[HeaderNames.IfModifiedSince]);
+ return null;
+ }
if (IsNotModified(ifModifiedSinceHeader, options.CacheDuration, options.DateLastModified))
{
@@ -629,7 +639,7 @@ namespace Emby.Server.Implementations.HttpServer
if (lastModifiedDate.HasValue)
{
- responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToString(CultureInfo.InvariantCulture);
+ responseHeaders[HeaderNames.LastModified] = lastModifiedDate.Value.ToUniversalTime().ToString(HttpDateFormat, _enUSculture);
}
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 4e2cf334c..50a5135d4 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -2364,7 +2364,7 @@ namespace Emby.Server.Implementations.Library
string videoPath,
string[] files)
{
- new SubtitleResolver(BaseItem.LocalizationManager, _fileSystem).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
+ new SubtitleResolver(BaseItem.LocalizationManager).AddExternalSubtitleStreams(streams, videoPath, streams.Count, files);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 4fdf73b77..1d61ed57e 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -39,7 +39,7 @@ namespace Emby.Server.Implementations.Library
// for imdbid we also accept pattern matching
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
{
- var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
+ var m = Regex.Match(str, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
return m.Success ? m.Value : null;
}
diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs
index 7b17cc913..15076a194 100644
--- a/Emby.Server.Implementations/Library/UserManager.cs
+++ b/Emby.Server.Implementations/Library/UserManager.cs
@@ -264,6 +264,7 @@ namespace Emby.Server.Implementations.Library
{
if (string.IsNullOrWhiteSpace(username))
{
+ _logger.LogInformation("Authentication request without username has been denied (IP: {IP}).", remoteEndPoint);
throw new ArgumentNullException(nameof(username));
}
@@ -319,11 +320,13 @@ namespace Emby.Server.Implementations.Library
if (user == null)
{
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Invalid username or password entered.");
}
if (user.Policy.IsDisabled)
{
+ _logger.LogInformation("Authentication request for {UserName} has been denied because this account is currently disabled (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException(
string.Format(
CultureInfo.InvariantCulture,
@@ -333,11 +336,13 @@ namespace Emby.Server.Implementations.Library
if (!user.Policy.EnableRemoteAccess && !_networkManager.IsInLocalNetwork(remoteEndPoint))
{
+ _logger.LogInformation("Authentication request for {UserName} forbidden: remote access disabled and user not in local network (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("Forbidden.");
}
if (!user.IsParentalScheduleAllowed())
{
+ _logger.LogInformation("Authentication request for {UserName} is not allowed at this time due parental restrictions (IP: {IP}).", username, remoteEndPoint);
throw new AuthenticationException("User is not allowed access at this time.");
}
@@ -351,14 +356,14 @@ namespace Emby.Server.Implementations.Library
}
ResetInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has succeeded.", user.Name);
}
else
{
IncrementInvalidLoginAttemptCount(user);
+ _logger.LogInformation("Authentication request for {UserName} has been denied (IP: {IP}).", user.Name, remoteEndPoint);
}
- _logger.LogInformation("Authentication request for {0} {1}.", user.Name, success ? "has succeeded" : "has been denied");
-
return success ? user : null;
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 6e903a18e..3b36247a9 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -22,6 +22,10 @@ namespace Emby.Server.Implementations.LiveTv
{
public class LiveTvDtoService
{
+ private const string InternalVersionNumber = "4";
+
+ private const string ServiceName = "Emby";
+
private readonly ILogger _logger;
private readonly IImageProcessor _imageProcessor;
@@ -32,13 +36,13 @@ namespace Emby.Server.Implementations.LiveTv
public LiveTvDtoService(
IDtoService dtoService,
IImageProcessor imageProcessor,
- ILoggerFactory loggerFactory,
+ ILogger<LiveTvDtoService> logger,
IApplicationHost appHost,
ILibraryManager libraryManager)
{
_dtoService = dtoService;
_imageProcessor = imageProcessor;
- _logger = loggerFactory.CreateLogger(nameof(LiveTvDtoService));
+ _logger = logger;
_appHost = appHost;
_libraryManager = libraryManager;
}
@@ -161,7 +165,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -179,6 +182,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -199,13 +203,12 @@ namespace Emby.Server.Implementations.LiveTv
var program = _libraryManager.GetItemList(new InternalItemsQuery
{
- IncludeItemTypes = new string[] { typeof(LiveTvProgram).Name },
+ IncludeItemTypes = new string[] { nameof(LiveTvProgram) },
ExternalSeriesId = programSeriesId,
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
-
}).FirstOrDefault();
if (program != null)
@@ -232,9 +235,10 @@ namespace Emby.Server.Implementations.LiveTv
try
{
dto.ParentBackdropImageTags = new string[]
- {
+ {
_imageProcessor.GetImageCacheTag(program, image)
- };
+ };
+
dto.ParentBackdropItemId = program.Id.ToString("N", CultureInfo.InvariantCulture);
}
catch (Exception ex)
@@ -255,7 +259,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Thumb },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (librarySeries != null)
@@ -273,6 +276,7 @@ namespace Emby.Server.Implementations.LiveTv
_logger.LogError(ex, "Error");
}
}
+
image = librarySeries.GetImageInfo(ImageType.Backdrop, 0);
if (image != null)
{
@@ -298,7 +302,6 @@ namespace Emby.Server.Implementations.LiveTv
Limit = 1,
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false)
-
}).FirstOrDefault();
if (program == null)
@@ -311,7 +314,6 @@ namespace Emby.Server.Implementations.LiveTv
ImageTypes = new ImageType[] { ImageType.Primary },
DtoOptions = new DtoOptions(false),
Name = string.IsNullOrEmpty(programSeriesId) ? seriesName : null
-
}).FirstOrDefault();
}
@@ -396,8 +398,6 @@ namespace Emby.Server.Implementations.LiveTv
return null;
}
- private const string InternalVersionNumber = "4";
-
public Guid GetInternalChannelId(string serviceName, string externalId)
{
var name = serviceName + externalId + InternalVersionNumber;
@@ -405,7 +405,6 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetNewItemId(name.ToLowerInvariant(), typeof(LiveTvChannel));
}
- private const string ServiceName = "Emby";
public string GetInternalTimerId(string externalId)
{
var name = ServiceName + externalId + InternalVersionNumber;
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index b64fe8634..bbc064a24 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -41,6 +41,10 @@ namespace Emby.Server.Implementations.LiveTv
/// </summary>
public class LiveTvManager : ILiveTvManager, IDisposable
{
+ private const string ExternalServiceTag = "ExternalServiceId";
+
+ private const string EtagKey = "ProgramEtag";
+
private readonly IServerConfigurationManager _config;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
@@ -91,7 +95,7 @@ namespace Emby.Server.Implementations.LiveTv
_userDataManager = userDataManager;
_channelManager = channelManager;
- _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory, appHost, _libraryManager);
+ _tvDtoService = new LiveTvDtoService(dtoService, imageProcessor, loggerFactory.CreateLogger<LiveTvDtoService>(), appHost, _libraryManager);
}
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCancelled;
@@ -178,7 +182,6 @@ namespace Emby.Server.Implementations.LiveTv
{
Name = i.Name,
Id = i.Type
-
}).ToList();
}
@@ -261,6 +264,7 @@ namespace Emby.Server.Implementations.LiveTv
var endTime = DateTime.UtcNow;
_logger.LogInformation("Live stream opened after {0}ms", (endTime - startTime).TotalMilliseconds);
}
+
info.RequiresClosing = true;
var idPrefix = service.GetType().FullName.GetMD5().ToString("N", CultureInfo.InvariantCulture) + "_";
@@ -362,30 +366,37 @@ namespace Emby.Server.Implementations.LiveTv
{
stream.BitRate = null;
}
+
if (stream.Channels.HasValue && stream.Channels <= 0)
{
stream.Channels = null;
}
+
if (stream.AverageFrameRate.HasValue && stream.AverageFrameRate <= 0)
{
stream.AverageFrameRate = null;
}
+
if (stream.RealFrameRate.HasValue && stream.RealFrameRate <= 0)
{
stream.RealFrameRate = null;
}
+
if (stream.Width.HasValue && stream.Width <= 0)
{
stream.Width = null;
}
+
if (stream.Height.HasValue && stream.Height <= 0)
{
stream.Height = null;
}
+
if (stream.SampleRate.HasValue && stream.SampleRate <= 0)
{
stream.SampleRate = null;
}
+
if (stream.Level.HasValue && stream.Level <= 0)
{
stream.Level = null;
@@ -427,7 +438,6 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- private const string ExternalServiceTag = "ExternalServiceId";
private LiveTvChannel GetChannel(ChannelInfo channelInfo, string serviceName, BaseItem parentFolder, CancellationToken cancellationToken)
{
var parentFolderId = parentFolder.Id;
@@ -456,6 +466,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.Tags = channelInfo.Tags;
}
@@ -463,6 +474,7 @@ namespace Emby.Server.Implementations.LiveTv
{
isNew = true;
}
+
item.ParentId = parentFolderId;
item.ChannelType = channelInfo.ChannelType;
@@ -472,24 +484,28 @@ namespace Emby.Server.Implementations.LiveTv
{
forceUpdate = true;
}
+
item.SetProviderId(ExternalServiceTag, serviceName);
if (!string.Equals(channelInfo.Id, item.ExternalId, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.ExternalId = channelInfo.Id;
if (!string.Equals(channelInfo.Number, item.Number, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.Number = channelInfo.Number;
if (!string.Equals(channelInfo.Name, item.Name, StringComparison.Ordinal))
{
forceUpdate = true;
}
+
item.Name = channelInfo.Name;
if (!item.HasImage(ImageType.Primary))
@@ -518,8 +534,6 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
- private const string EtagKey = "ProgramEtag";
-
private Tuple<LiveTvProgram, bool, bool> GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel, ChannelType channelType, string serviceName, CancellationToken cancellationToken)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 2d8299367..7464ac1c0 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -3,19 +3,19 @@
"AppDeviceValues": "Aplicació: {0}, Dispositiu: {1}",
"Application": "Aplicació",
"Artists": "Artistes",
- "AuthenticationSucceededWithUserName": "{0} s'ha autentificat correctament",
+ "AuthenticationSucceededWithUserName": "{0} s'ha autenticat correctament",
"Books": "Llibres",
- "CameraImageUploadedFrom": "Una nova imatge de la càmera ha sigut pujada des de {0}",
+ "CameraImageUploadedFrom": "Una nova imatge de la càmera ha estat pujada des de {0}",
"Channels": "Canals",
- "ChapterNameValue": "Episodi {0}",
+ "ChapterNameValue": "Capítol {0}",
"Collections": "Col·leccions",
"DeviceOfflineWithName": "{0} s'ha desconnectat",
"DeviceOnlineWithName": "{0} està connectat",
"FailedLoginAttemptWithUserName": "Intent de connexió fallit des de {0}",
"Favorites": "Preferits",
- "Folders": "Directoris",
+ "Folders": "Carpetes",
"Genres": "Gèneres",
- "HeaderAlbumArtists": "Artistes dels Àlbums",
+ "HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderCameraUploads": "Pujades de Càmera",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",
diff --git a/Emby.Server.Implementations/Localization/Core/da.json b/Emby.Server.Implementations/Localization/Core/da.json
index 93b8052d3..f5397b62c 100644
--- a/Emby.Server.Implementations/Localization/Core/da.json
+++ b/Emby.Server.Implementations/Localization/Core/da.json
@@ -35,8 +35,8 @@
"Latest": "Seneste",
"MessageApplicationUpdated": "Jellyfin Server er blevet opdateret",
"MessageApplicationUpdatedTo": "Jellyfin Server er blevet opdateret til {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Serverkonfigurationsafsnit {0} er blevet opdateret",
- "MessageServerConfigurationUpdated": "Serverkonfigurationen er blevet opdateret",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Server konfiguration sektion {0} er blevet opdateret",
+ "MessageServerConfigurationUpdated": "Server konfigurationen er blevet opdateret",
"MixedContent": "Blandet indhold",
"Movies": "Film",
"Music": "Musik",
@@ -93,13 +93,13 @@
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
"ValueSpecialEpisodeName": "Special - {0}",
"VersionNumber": "Version {0}",
- "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
+ "TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiguration.",
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
"TaskUpdatePlugins": "Opdater Plugins",
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
"TaskCleanLogs": "Ryd Log Mappe",
- "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdatere metadata.",
+ "TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdaterer metadata.",
"TaskRefreshLibrary": "Scan Medie Bibliotek",
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
"TaskCleanCache": "Ryd Cache Mappe",
@@ -108,5 +108,11 @@
"TasksLibraryCategory": "Bibliotek",
"TasksMaintenanceCategory": "Vedligeholdelse",
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
- "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
+ "TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler.",
+ "TaskRefreshChannelsDescription": "Genopfrisker internet kanal information.",
+ "TaskRefreshChannels": "Genopfrisk Kanaler",
+ "TaskCleanTranscodeDescription": "Fjern transcode filer som er mere end en dag gammel.",
+ "TaskCleanTranscode": "Rengør Transcode Mappen",
+ "TaskRefreshPeople": "Genopfrisk Personer",
+ "TaskRefreshPeopleDescription": "Opdatere metadata for skuespillere og instruktører i dit bibliotek."
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 414430ff7..82df43be1 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -3,7 +3,7 @@
"AppDeviceValues": "App: {0}, Gerät: {1}",
"Application": "Anwendung",
"Artists": "Interpreten",
- "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifziert",
+ "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
@@ -99,11 +99,11 @@
"TaskRefreshChannels": "Erneuere Kanäle",
"TaskCleanTranscodeDescription": "Löscht Transkodierdateien welche älter als ein Tag sind.",
"TaskCleanTranscode": "Lösche Transkodier Pfad",
- "TaskUpdatePluginsDescription": "Läd Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
+ "TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche dazu eingestellt sind automatisch zu updaten und installiert sie.",
"TaskUpdatePlugins": "Update Plugins",
"TaskRefreshPeopleDescription": "Erneuert Metadaten für Schausteller und Regisseure in deinen Bibliotheken.",
"TaskRefreshPeople": "Erneuere Schausteller",
- "TaskCleanLogsDescription": "Lösche Log Datein die älter als {0} Tage sind.",
+ "TaskCleanLogsDescription": "Lösche Log Dateien die älter als {0} Tage sind.",
"TaskCleanLogs": "Lösche Log Pfad",
"TaskRefreshLibraryDescription": "Scanne alle Bibliotheken für hinzugefügte Datein und erneuere Metadaten.",
"TaskRefreshLibrary": "Scanne alle Bibliotheken",
diff --git a/Emby.Server.Implementations/Localization/Core/es-AR.json b/Emby.Server.Implementations/Localization/Core/es-AR.json
index 1211eef54..1b6c6b5ae 100644
--- a/Emby.Server.Implementations/Localization/Core/es-AR.json
+++ b/Emby.Server.Implementations/Localization/Core/es-AR.json
@@ -17,7 +17,7 @@
"Genres": "Géneros",
"HeaderAlbumArtists": "Artistas de álbum",
"HeaderCameraUploads": "Subidas de cámara",
- "HeaderContinueWatching": "Continuar viendo",
+ "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
diff --git a/Emby.Server.Implementations/Localization/Core/fr.json b/Emby.Server.Implementations/Localization/Core/fr.json
index d1403c494..150952d8b 100644
--- a/Emby.Server.Implementations/Localization/Core/fr.json
+++ b/Emby.Server.Implementations/Localization/Core/fr.json
@@ -11,11 +11,11 @@
"Collections": "Collections",
"DeviceOfflineWithName": "{0} s'est déconnecté",
"DeviceOnlineWithName": "{0} est connecté",
- "FailedLoginAttemptWithUserName": "Échec de connexion de {0}",
+ "FailedLoginAttemptWithUserName": "Échec de connexion depuis {0}",
"Favorites": "Favoris",
"Folders": "Dossiers",
"Genres": "Genres",
- "HeaderAlbumArtists": "Artistes d'album",
+ "HeaderAlbumArtists": "Artistes de l'album",
"HeaderCameraUploads": "Photos transférées",
"HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris",
@@ -69,7 +69,7 @@
"PluginUpdatedWithName": "{0} a été mis à jour",
"ProviderValue": "Fournisseur : {0}",
"ScheduledTaskFailedWithName": "{0} a échoué",
- "ScheduledTaskStartedWithName": "{0} a commencé",
+ "ScheduledTaskStartedWithName": "{0} a démarré",
"ServerNameNeedsToBeRestarted": "{0} doit être redémarré",
"Shows": "Émissions",
"Songs": "Chansons",
@@ -95,21 +95,21 @@
"VersionNumber": "Version {0}",
"TasksChannelsCategory": "Chaines en ligne",
"TaskDownloadMissingSubtitlesDescription": "Cherche les sous-titres manquant sur internet en se basant sur la configuration des métadonnées.",
- "TaskDownloadMissingSubtitles": "Télécharge les sous-titres manquant",
+ "TaskDownloadMissingSubtitles": "Télécharger les sous-titres manquant",
"TaskRefreshChannelsDescription": "Rafraîchit les informations des chaines en ligne.",
- "TaskRefreshChannels": "Rafraîchit les chaines",
+ "TaskRefreshChannels": "Rafraîchir les chaines",
"TaskCleanTranscodeDescription": "Supprime les fichiers transcodés de plus d'un jour.",
- "TaskCleanTranscode": "Nettoie les dossier des transcodages",
- "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des plugins configurés pour être mis à jour automatiquement.",
- "TaskUpdatePlugins": "Mettre à jour les plugins",
- "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et directeurs dans votre bibliothèque.",
- "TaskRefreshPeople": "Rafraîchit les acteurs",
+ "TaskCleanTranscode": "Nettoyer les dossier des transcodages",
+ "TaskUpdatePluginsDescription": "Télécharge et installe les mises à jours des extensions configurés pour être mises à jour automatiquement.",
+ "TaskUpdatePlugins": "Mettre à jour les extensions",
+ "TaskRefreshPeopleDescription": "Met à jour les métadonnées pour les acteurs et réalisateurs dans votre bibliothèque.",
+ "TaskRefreshPeople": "Rafraîchir les acteurs",
"TaskCleanLogsDescription": "Supprime les journaux de plus de {0} jours.",
- "TaskCleanLogs": "Nettoie le répertoire des journaux",
+ "TaskCleanLogs": "Nettoyer le répertoire des journaux",
"TaskRefreshLibraryDescription": "Scanne toute les bibliothèques pour trouver les nouveaux fichiers et rafraîchit les métadonnées.",
- "TaskRefreshLibrary": "Scanne toute les Bibliothèques",
+ "TaskRefreshLibrary": "Scanner toute les Bibliothèques",
"TaskRefreshChapterImagesDescription": "Crée des images de miniature pour les vidéos ayant des chapitres.",
- "TaskRefreshChapterImages": "Extrait les images de chapitre",
+ "TaskRefreshChapterImages": "Extraire les images de chapitre",
"TaskCleanCacheDescription": "Supprime les fichiers de cache dont le système n'a plus besoin.",
"TaskCleanCache": "Vider le répertoire cache",
"TasksApplicationCategory": "Application",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index d0daed7a3..5e017d4c4 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -109,5 +109,8 @@
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
"TaskUpdatePlugins": "プラグインの更新",
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
- "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
+ "TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
+ "TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
+ "TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
+ "TaskRefreshChapterImages": "チャプター画像を抽出する"
}
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 661ee8603..25c5b9053 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -95,5 +95,13 @@
"TaskCleanCache": "Limpar Diretório de Cache",
"TasksApplicationCategory": "Aplicação",
"TasksLibraryCategory": "Biblioteca",
- "TasksMaintenanceCategory": "Manutenção"
+ "TasksMaintenanceCategory": "Manutenção",
+ "TaskRefreshChannels": "Atualizar Canais",
+ "TaskUpdatePlugins": "Atualizar Plugins",
+ "TaskCleanLogsDescription": "Deletar arquivos de log que existe a mais de {0} dias.",
+ "TaskCleanLogs": "Limpar diretório de log",
+ "TaskRefreshLibrary": "Escanear biblioteca de mídias",
+ "TaskRefreshChapterImagesDescription": "Criar miniaturas para videos que tem capítulos.",
+ "TaskCleanCacheDescription": "Deletar arquivos de cache que não são mais usados pelo sistema.",
+ "TasksChannelsCategory": "Canais de Internet"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index c46aa5c30..71ee6446c 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -9,8 +9,8 @@
"Channels": "Каналы",
"ChapterNameValue": "Сцена {0}",
"Collections": "Коллекции",
- "DeviceOfflineWithName": "{0} - подкл. разъ-но",
- "DeviceOnlineWithName": "{0} - подкл. уст-но",
+ "DeviceOfflineWithName": "{0} - отключено",
+ "DeviceOnlineWithName": "{0} - подключено",
"FailedLoginAttemptWithUserName": "{0} - попытка входа неудачна",
"Favorites": "Избранное",
"Folders": "Папки",
@@ -26,30 +26,30 @@
"HeaderLiveTV": "Эфир",
"HeaderNextUp": "Очередное",
"HeaderRecordingGroups": "Группы записей",
- "HomeVideos": "Дом. видео",
+ "HomeVideos": "Домашнее видео",
"Inherit": "Наследуемое",
"ItemAddedWithName": "{0} - добавлено в медиатеку",
"ItemRemovedWithName": "{0} - изъято из медиатеки",
"LabelIpAddressValue": "IP-адрес: {0}",
"LabelRunningTimeValue": "Длительность: {0}",
- "Latest": "Новейшее",
+ "Latest": "Последнее",
"MessageApplicationUpdated": "Jellyfin Server был обновлён",
"MessageApplicationUpdatedTo": "Jellyfin Server был обновлён до {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Конфиг-ия сервера (раздел {0}) была обновлена",
- "MessageServerConfigurationUpdated": "Конфиг-ия сервера была обновлена",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Конфигурация сервера (раздел {0}) была обновлена",
+ "MessageServerConfigurationUpdated": "Конфигурация сервера была обновлена",
"MixedContent": "Смешанное содержимое",
"Movies": "Кино",
"Music": "Музыка",
- "MusicVideos": "Муз. видео",
+ "MusicVideos": "Музыкальные клипы",
"NameInstallFailed": "Установка {0} неудачна",
"NameSeasonNumber": "Сезон {0}",
"NameSeasonUnknown": "Сезон неопознан",
"NewVersionIsAvailable": "Новая версия Jellyfin Server доступна для загрузки.",
"NotificationOptionApplicationUpdateAvailable": "Имеется обновление приложения",
"NotificationOptionApplicationUpdateInstalled": "Обновление приложения установлено",
- "NotificationOptionAudioPlayback": "Воспр-ие аудио зап-но",
- "NotificationOptionAudioPlaybackStopped": "Восп-ие аудио ост-но",
- "NotificationOptionCameraImageUploaded": "Произведена выкладка отснятого с камеры",
+ "NotificationOptionAudioPlayback": "Воспроизведение аудио запущено",
+ "NotificationOptionAudioPlaybackStopped": "Воспроизведение аудио остановлено",
+ "NotificationOptionCameraImageUploaded": "Изображения с камеры загружены",
"NotificationOptionInstallationFailed": "Сбой установки",
"NotificationOptionNewLibraryContent": "Новое содержание добавлено",
"NotificationOptionPluginError": "Сбой плагина",
@@ -59,8 +59,8 @@
"NotificationOptionServerRestartRequired": "Требуется перезапуск сервера",
"NotificationOptionTaskFailed": "Сбой назначенной задачи",
"NotificationOptionUserLockedOut": "Пользователь заблокирован",
- "NotificationOptionVideoPlayback": "Воспр-ие видео зап-но",
- "NotificationOptionVideoPlaybackStopped": "Восп-ие видео ост-но",
+ "NotificationOptionVideoPlayback": "Воспроизведение видео запущено",
+ "NotificationOptionVideoPlaybackStopped": "Воспроизведение видео остановлено",
"Photos": "Фото",
"Playlists": "Плей-листы",
"Plugin": "Плагин",
@@ -76,21 +76,43 @@
"StartupEmbyServerIsLoading": "Jellyfin Server загружается. Повторите попытку в ближайшее время.",
"SubtitleDownloadFailureForItem": "Субтитры к {0} не удалось загрузить",
"SubtitleDownloadFailureFromForItem": "Субтитры к {1} не удалось загрузить с {0}",
- "Sync": "Синхро",
+ "Sync": "Синхронизация",
"System": "Система",
"TvShows": "ТВ",
- "User": "Польз-ль",
+ "User": "Пользователь",
"UserCreatedWithName": "Пользователь {0} был создан",
"UserDeletedWithName": "Пользователь {0} был удалён",
"UserDownloadingItemWithValues": "{0} загружает {1}",
"UserLockedOutWithName": "Пользователь {0} был заблокирован",
- "UserOfflineFromDevice": "{0} - подкл. с {1} разъ-но",
- "UserOnlineFromDevice": "{0} - подкл. с {1} уст-но",
- "UserPasswordChangedWithName": "Пароль польз-ля {0} был изменён",
- "UserPolicyUpdatedWithName": "Польз-ие политики {0} были обновлены",
- "UserStartedPlayingItemWithValues": "{0} - воспр. «{1}» на {2}",
- "UserStoppedPlayingItemWithValues": "{0} - воспр. «{1}» ост-но на {2}",
+ "UserOfflineFromDevice": "{0} отключился с {1}",
+ "UserOnlineFromDevice": "{0} подключился с {1}",
+ "UserPasswordChangedWithName": "Пароль пользователя {0} был изменён",
+ "UserPolicyUpdatedWithName": "Политики пользователя {0} были обновлены",
+ "UserStartedPlayingItemWithValues": "{0} - воспроизведение «{1}» на {2}",
+ "UserStoppedPlayingItemWithValues": "{0} - воспроизведение остановлено «{1}» на {2}",
"ValueHasBeenAddedToLibrary": "{0} (добавлено в медиатеку)",
- "ValueSpecialEpisodeName": "Спецэпизод - {0}",
- "VersionNumber": "Версия {0}"
+ "ValueSpecialEpisodeName": "Специальный эпизод - {0}",
+ "VersionNumber": "Версия {0}",
+ "TaskDownloadMissingSubtitles": "Загрузка отсутствующих субтитров",
+ "TaskRefreshChannels": "Обновление каналов",
+ "TaskCleanTranscode": "Очистка каталога перекодировки",
+ "TaskUpdatePlugins": "Обновление плагинов",
+ "TaskRefreshPeople": "Обновление метаданных людей",
+ "TaskCleanLogs": "Очистка каталога журналов",
+ "TaskRefreshLibrary": "Сканирование медиатеки",
+ "TaskRefreshChapterImages": "Извлечение изображений сцен",
+ "TaskCleanCache": "Очистка каталога кеша",
+ "TasksChannelsCategory": "Интернет-каналы",
+ "TasksApplicationCategory": "Приложение",
+ "TasksLibraryCategory": "Медиатека",
+ "TasksMaintenanceCategory": "Обслуживание",
+ "TaskDownloadMissingSubtitlesDescription": "Выполняется поиск в Интернете отсутствующих субтитров на основе конфигурации метаданных.",
+ "TaskRefreshChannelsDescription": "Обновляются данные интернет-каналов.",
+ "TaskCleanTranscodeDescription": "Удаляются файлы перекодировки старше одного дня.",
+ "TaskUpdatePluginsDescription": "Загружаются и устанавливаются обновления для плагинов, у которых включено автоматическое обновление.",
+ "TaskRefreshPeopleDescription": "Обновляются метаданные актеров и режиссёров в медиатеке.",
+ "TaskCleanLogsDescription": "Удаляются файлы журнала, возраст которых превышает {0} дн(я/ей).",
+ "TaskRefreshLibraryDescription": "Сканируется медиатека на новые файлы и обновляются метаданные.",
+ "TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
+ "TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе."
}
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index 21034b76f..c423c7ea7 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -20,7 +20,7 @@
"HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者",
- "HeaderFavoriteEpisodes": "最愛級數",
+ "HeaderFavoriteEpisodes": "最愛影集",
"HeaderFavoriteShows": "最愛節目",
"HeaderFavoriteSongs": "最愛歌曲",
"HeaderLiveTV": "電視直播",
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index dd4f9cd23..71ef9a69a 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -71,6 +71,11 @@ namespace Jellyfin.Server.Extensions
// Clear app parts to avoid other assemblies being picked up
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
.AddApplicationPart(typeof(StartupController).Assembly)
+ .AddJsonOptions(options =>
+ {
+ // Setting the naming policy to null leaves the property names as-is when serializing objects to JSON.
+ options.JsonSerializerOptions.PropertyNamingPolicy = null;
+ })
.AddControllersAsServices();
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index eb44cb426..770539357 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -134,7 +134,7 @@ namespace MediaBrowser.Api.Playback
var data = $"{state.MediaPath}-{state.UserAgent}-{state.Request.DeviceId}-{state.Request.PlaySessionId}";
var filename = data.GetMD5().ToString("N", CultureInfo.InvariantCulture);
- var ext = outputFileExtension.ToLowerInvariant();
+ var ext = outputFileExtension?.ToLowerInvariant();
var folder = ServerConfigurationManager.GetTranscodePath();
return EnableOutputInSubFolder
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 3638feaa3..db24eaca6 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -520,10 +520,7 @@ namespace MediaBrowser.Api.Playback
streamInfo.StartPositionTicks = startTimeTicks;
mediaSource.TranscodingUrl = streamInfo.ToUrl("-", auth.Token).TrimStart('-');
mediaSource.TranscodingUrl += "&allowVideoStreamCopy=false";
- if (!allowAudioStreamCopy)
- {
- mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
- }
+ mediaSource.TranscodingUrl += "&allowAudioStreamCopy=false";
mediaSource.TranscodingContainer = streamInfo.Container;
mediaSource.TranscodingSubProtocol = streamInfo.SubProtocol;
diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs
index ac59c3030..c4d44042b 100644
--- a/MediaBrowser.Api/UserLibrary/ItemsService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs
@@ -242,11 +242,11 @@ namespace MediaBrowser.Api.UserLibrary
return folder.GetItems(GetItemsQuery(request, dtoOptions, user));
}
- var itemsArray = folder.GetChildren(user, true).ToArray();
+ var itemsArray = folder.GetChildren(user, true);
return new QueryResult<BaseItem>
{
Items = itemsArray,
- TotalRecordCount = itemsArray.Length,
+ TotalRecordCount = itemsArray.Count,
StartIndex = 0
};
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 56a361e0e..7ed8fa767 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -549,7 +549,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public DateTime DateModified { get; set; }
- [JsonIgnore]
public DateTime DateLastSaved { get; set; }
[JsonIgnore]
@@ -2741,7 +2740,7 @@ namespace MediaBrowser.Controller.Entities
{
var list = GetEtagValues(user);
- return string.Join("|", list.ToArray()).GetMD5().ToString("N", CultureInfo.InvariantCulture);
+ return string.Join("|", list).GetMD5().ToString("N", CultureInfo.InvariantCulture);
}
protected virtual List<string> GetEtagValues(User user)
@@ -2784,8 +2783,7 @@ namespace MediaBrowser.Controller.Entities
return true;
}
- var view = this as IHasCollectionType;
- if (view != null)
+ if (this is IHasCollectionType view)
{
if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index bb48605e5..a468e0c35 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -864,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
return SortItemsByRequest(query, result);
}
- return result.ToArray();
+ return result;
}
return GetItemsInternal(query).Items;
diff --git a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
index 4424e044b..c2dcb66d7 100644
--- a/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
@@ -6,30 +6,34 @@ using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Persistence
{
/// <summary>
- /// Interface IDisplayPreferencesRepository
+ /// Interface IDisplayPreferencesRepository.
/// </summary>
public interface IDisplayPreferencesRepository : IRepository
{
/// <summary>
- /// Saves display preferences for an item
+ /// Saves display preferences for an item.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="client">The client.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- void SaveDisplayPreferences(DisplayPreferences displayPreferences, string userId, string client,
- CancellationToken cancellationToken);
+ void SaveDisplayPreferences(
+ DisplayPreferences displayPreferences,
+ string userId,
+ string client,
+ CancellationToken cancellationToken);
/// <summary>
- /// Saves all display preferences for a user
+ /// Saves all display preferences for a user.
/// </summary>
/// <param name="displayPreferences">The display preferences.</param>
/// <param name="userId">The user id.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- void SaveAllDisplayPreferences(IEnumerable<DisplayPreferences> displayPreferences, Guid userId,
- CancellationToken cancellationToken);
+ void SaveAllDisplayPreferences(
+ IEnumerable<DisplayPreferences> displayPreferences,
+ Guid userId,
+ CancellationToken cancellationToken);
+
/// <summary>
/// Gets the display preferences.
/// </summary>
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index c2bfac9d6..359330449 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -478,7 +478,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
catch (Exception ex)
{
- _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {arguments}", inputArgument);
+ _logger.LogError(ex, "I-frame image extraction failed, will attempt standard way. Input: {Arguments}", inputArgument);
}
}
@@ -969,7 +969,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
public int? ExitCode { get; private set; }
- void OnProcessExited(object sender, EventArgs e)
+ private void OnProcessExited(object sender, EventArgs e)
{
var process = (Process)sender;
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index 3107ec242..b5e8d5589 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -15,9 +15,8 @@ namespace MediaBrowser.Model.Configuration
private string _baseUrl;
/// <summary>
- /// Gets or sets a value indicating whether [enable u pn p].
+ /// Gets or sets a value indicating whether to enable automatic port forwarding.
/// </summary>
- /// <value><c>true</c> if [enable u pn p]; otherwise, <c>false</c>.</value>
public bool EnableUPnP { get; set; }
/// <summary>
@@ -254,7 +253,7 @@ namespace MediaBrowser.Model.Configuration
AutoRunWebApp = true;
EnableRemoteAccess = true;
- EnableUPnP = true;
+ EnableUPnP = false;
MinResumePct = 5;
MaxResumePct = 90;
diff --git a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
index e30dd84dc..29f417489 100644
--- a/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
+++ b/MediaBrowser.Model/LiveTv/SeriesTimerInfoDto.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Model.LiveTv
public SeriesTimerInfoDto()
{
ImageTags = new Dictionary<ImageType, string>();
- Days = new DayOfWeek[] { };
+ Days = Array.Empty<DayOfWeek>();
Type = "SeriesTimer";
}
diff --git a/MediaBrowser.Model/Net/MimeTypes.cs b/MediaBrowser.Model/Net/MimeTypes.cs
index 68bcc590c..06efedb30 100644
--- a/MediaBrowser.Model/Net/MimeTypes.cs
+++ b/MediaBrowser.Model/Net/MimeTypes.cs
@@ -106,6 +106,7 @@ namespace MediaBrowser.Model.Net
{ ".3g2", "video/3gpp2" },
{ ".mpd", "video/vnd.mpeg.dash.mpd" },
{ ".ts", "video/mp2t" },
+ { ".mpegts", "video/mp2t" },
// Type audio
{ ".mp3", "audio/mpeg" },
@@ -123,6 +124,8 @@ namespace MediaBrowser.Model.Net
{ ".xsp", "audio/xsp" },
{ ".dsp", "audio/dsp" },
{ ".flac", "audio/flac" },
+ { ".ape", "audio/x-ape" },
+ { ".wv", "audio/x-wavpack" },
};
private static readonly Dictionary<string, string> _extensionLookup = CreateExtensionLookup();
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
index 3999025d8..6982568eb 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
@@ -46,7 +46,6 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IApplicationPaths _appPaths;
private readonly IJsonSerializer _json;
private readonly IEncodingManager _encodingManager;
- private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager;
@@ -134,7 +133,6 @@ namespace MediaBrowser.Providers.MediaInfo
IApplicationPaths appPaths,
IJsonSerializer json,
IEncodingManager encodingManager,
- IFileSystem fileSystem,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
@@ -149,7 +147,6 @@ namespace MediaBrowser.Providers.MediaInfo
_appPaths = appPaths;
_json = json;
_encodingManager = encodingManager;
- _fileSystem = fileSystem;
_config = config;
_subtitleManager = subtitleManager;
_chapterManager = chapterManager;
@@ -157,7 +154,7 @@ namespace MediaBrowser.Providers.MediaInfo
_channelManager = channelManager;
_mediaSourceManager = mediaSourceManager;
- _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager, fileSystem);
+ _subtitleResolver = new SubtitleResolver(BaseItem.LocalizationManager);
}
private readonly Task<ItemUpdateType> _cachedTask = Task.FromResult(ItemUpdateType.None);
@@ -202,7 +199,6 @@ namespace MediaBrowser.Providers.MediaInfo
_blurayExaminer,
_localization,
_encodingManager,
- _fileSystem,
_config,
_subtitleManager,
_chapterManager,
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index d2e98a5a9..89496622f 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -39,7 +39,6 @@ namespace MediaBrowser.Providers.MediaInfo
private readonly IBlurayExaminer _blurayExaminer;
private readonly ILocalizationManager _localization;
private readonly IEncodingManager _encodingManager;
- private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
private readonly ISubtitleManager _subtitleManager;
private readonly IChapterManager _chapterManager;
@@ -56,7 +55,6 @@ namespace MediaBrowser.Providers.MediaInfo
IBlurayExaminer blurayExaminer,
ILocalizationManager localization,
IEncodingManager encodingManager,
- IFileSystem fileSystem,
IServerConfigurationManager config,
ISubtitleManager subtitleManager,
IChapterManager chapterManager,
@@ -68,7 +66,6 @@ namespace MediaBrowser.Providers.MediaInfo
_blurayExaminer = blurayExaminer;
_localization = localization;
_encodingManager = encodingManager;
- _fileSystem = fileSystem;
_config = config;
_subtitleManager = subtitleManager;
_chapterManager = chapterManager;
@@ -76,7 +73,8 @@ namespace MediaBrowser.Providers.MediaInfo
_mediaSourceManager = mediaSourceManager;
}
- public async Task<ItemUpdateType> ProbeVideo<T>(T item,
+ public async Task<ItemUpdateType> ProbeVideo<T>(
+ T item,
MetadataRefreshOptions options,
CancellationToken cancellationToken)
where T : Video
@@ -99,7 +97,6 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.MetadataImport;
}
}
-
else if (item.VideoType == VideoType.BluRay)
{
var inputPath = item.Path;
@@ -130,7 +127,8 @@ namespace MediaBrowser.Providers.MediaInfo
return ItemUpdateType.MetadataImport;
}
- private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(Video item,
+ private Task<Model.MediaInfo.MediaInfo> GetMediaInfo(
+ Video item,
string[] streamFileNames,
CancellationToken cancellationToken)
{
@@ -145,22 +143,24 @@ namespace MediaBrowser.Providers.MediaInfo
protocol = _mediaSourceManager.GetPathProtocol(path);
}
- return _mediaEncoder.GetMediaInfo(new MediaInfoRequest
- {
- PlayableStreamFileNames = streamFileNames,
- ExtractChapters = true,
- MediaType = DlnaProfileType.Video,
- MediaSource = new MediaSourceInfo
+ return _mediaEncoder.GetMediaInfo(
+ new MediaInfoRequest
{
- Path = path,
- Protocol = protocol,
- VideoType = item.VideoType
- }
-
- }, cancellationToken);
+ PlayableStreamFileNames = streamFileNames,
+ ExtractChapters = true,
+ MediaType = DlnaProfileType.Video,
+ MediaSource = new MediaSourceInfo
+ {
+ Path = path,
+ Protocol = protocol,
+ VideoType = item.VideoType
+ }
+ },
+ cancellationToken);
}
- protected async Task Fetch(Video video,
+ protected async Task Fetch(
+ Video video,
CancellationToken cancellationToken,
Model.MediaInfo.MediaInfo mediaInfo,
BlurayDiscInfo blurayInfo,
@@ -491,12 +491,13 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="options">The refreshOptions.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task AddExternalSubtitles(Video video,
+ private async Task AddExternalSubtitles(
+ Video video,
List<MediaStream> currentStreams,
MetadataRefreshOptions options,
CancellationToken cancellationToken)
{
- var subtitleResolver = new SubtitleResolver(_localization, _fileSystem);
+ var subtitleResolver = new SubtitleResolver(_localization);
var startIndex = currentStreams.Count == 0 ? 0 : (currentStreams.Select(i => i.Index).Max() + 1);
var externalSubtitleStreams = subtitleResolver.GetExternalSubtitleStreams(video, startIndex, options.DirectoryService, false);
@@ -605,7 +606,7 @@ namespace MediaBrowser.Providers.MediaInfo
private string[] FetchFromDvdLib(Video item)
{
var path = item.Path;
- var dvd = new Dvd(path, _fileSystem);
+ var dvd = new Dvd(path);
var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
diff --git a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
index 7ebbb9e23..2bbe8a968 100644
--- a/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
+++ b/MediaBrowser.Providers/MediaInfo/SubtitleResolver.cs
@@ -13,7 +13,6 @@ namespace MediaBrowser.Providers.MediaInfo
public class SubtitleResolver
{
private readonly ILocalizationManager _localization;
- private readonly IFileSystem _fileSystem;
private static readonly HashSet<string> SubtitleExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
@@ -26,16 +25,16 @@ namespace MediaBrowser.Providers.MediaInfo
".vtt"
};
- public SubtitleResolver(ILocalizationManager localization, IFileSystem fileSystem)
+ public SubtitleResolver(ILocalizationManager localization)
{
_localization = localization;
- _fileSystem = fileSystem;
}
- public List<MediaStream> GetExternalSubtitleStreams(Video video,
- int startIndex,
- IDirectoryService directoryService,
- bool clearCache)
+ public List<MediaStream> GetExternalSubtitleStreams(
+ Video video,
+ int startIndex,
+ IDirectoryService directoryService,
+ bool clearCache)
{
var streams = new List<MediaStream>();
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index 36b9a9c1f..5c8de80f1 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -208,8 +208,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
protected void ParseProviderLinks(T item, string xml)
{
- // Look for a match for the Regex pattern "tt" followed by 7 digits
- var m = Regex.Match(xml, @"tt([0-9]{7})", RegexOptions.IgnoreCase);
+ // Look for a match for the Regex pattern "tt" followed by 7 or 8 digits
+ var m = Regex.Match(xml, "tt([0-9]{7,8})", RegexOptions.IgnoreCase);
if (m.Success)
{
item.SetProviderId(MetadataProviders.Imdb, m.Value);
diff --git a/README.md b/README.md
index 681076e3e..99a66e306 100644
--- a/README.md
+++ b/README.md
@@ -69,3 +69,99 @@ Most of the translations can be found in the web client but we have several othe
<a href="https://translate.jellyfin.org/engage/jellyfin/?utm_source=widget">
<img src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-web/multi-auto.svg" alt="Detailed Translation Status"/>
</a>
+
+## Jellyfin Server
+
+This repository contains the code for Jellyfin's backend server. Note that this is only one of many projects under the Jellyfin GitHub [organization](https://github.com/jellyfin/) on GitHub. If you want to contribute, you can start by checking out our [documentation](https://jellyfin.org/docs/general/contributing/index.html) to see what to work on.
+
+## Server Development
+
+These instructions will help you get set up with a local development environment in order to contribute to this repository. Before you start, please be sure to completely read our [guidelines on development contributions](https://jellyfin.org/docs/general/contributing/development.html). Note that this project is supported on all major operating systems except FreeBSD, which is still incompatible.
+
+### Prerequisites
+
+Before the project can be built, you must first install the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) on your system.
+
+Instructions to run this project from the command line are included here, but you will also need to install an IDE if you want to debug the server while it is running. Any IDE that supports .NET Core development will work, but two options are recent versions of [Visual Studio](https://visualstudio.microsoft.com/downloads/) (at least 2017) and [Visual Studio Code](https://code.visualstudio.com/Download).
+
+### Cloning the Repository
+
+After dependencies are installed you will need to clone a local copy of this repository. If you just want to run the server from source you can clone this repository directly, but if you are intending to contribute code changes to the project, you should [set up your own fork](https://jellyfin.org/docs/general/contributing/development.html#set-up-your-copy-of-the-repo) of the repository. The following example shows how you can clone the repository directly over HTTPS.
+
+```bash
+git clone https://github.com/jellyfin/jellyfin.git
+```
+
+### Installing the Web Client
+
+The server is configured to host the static files required for the [web client](https://github.com/jellyfin/jellyfin-web) in addition to serving the backend by default. Before you can run the server, you will need to get a copy of the web client since they are not included in this repository directly.
+
+Note that it is also possible to [host the web client separately](#hosting-the-web-client-separately) from the web server with some additional configuration, in which case you can skip this step.
+
+There are three options to get the files for the web client.
+
+1. Download one of the finished builds from the [Azure DevOps pipeline](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11). You can download the build for a specific release by looking at the [branches tab](https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=11&_a=summary&repositoryFilter=6&view=branches) of the pipelines page.
+2. Build them from source following the instructions on the [jellyfin-web repository](https://github.com/jellyfin/jellyfin-web)
+3. Get the pre-built files from an existing installation of the server. For example, with a Windows server installation the client files are located at `C:\Program Files\Jellyfin\Server\jellyfin-web`
+
+Once you have a copy of the built web client files, you need to copy them into a specific directory.
+
+> `<repository root>/Mediabrowser.WebDashboard/jellyfin-web`
+
+As part of the build process, this folder will be copied to the build output directory, where it can be accessed by the server.
+
+### Running The Server
+
+The following instructions will help you get the project up and running via the command line, or your preferred IDE.
+
+#### Running With Visual Studio
+
+To run the project with Visual Studio you can open the Solution (`.sln`) file and then press `F5` to run the server.
+
+#### Running With Visual Studio Code
+
+To run the project with Visual Studio Code you will first need to open the repository directory with Visual Studio Code using the `Open Folder...` option.
+
+Second, you need to [install the recommended extensions for the workspace](https://code.visualstudio.com/docs/editor/extension-gallery#_recommended-extensions). Note that extension recommendations are classified as either "Workspace Recommendations" or "Other Recommendations", but only the "Workspace Recommendations" are required.
+
+After the required extensions are installed, you can can run the server by pressing `F5`.
+
+#### Running From The Command Line
+
+To run the server from the command line you can use the `dotnet run` command. The example below shows how to do this if you have cloned the repository into a directory named `jellyfin` (the default directory name) and should work on all operating systems.
+
+```bash
+cd jellyfin # Move into the repository directory
+dotnet run --project Jellyfin.Server # Run the server startup project
+```
+
+A second option is to build the project and then run the resulting executable file directly. When running the executable directly you can easily add command line options. Add the `--help` flag to list details on all the supported command line options.
+
+1. Build the project
+
+ ```bash
+ dotnet build # Build the project
+ cd bin/Debug/netcoreapp3.1 # Change into the build output directory
+ ```
+
+2. Execute the build output. On Linux, Mac, etc. use `./jellyfin` and on Windows use `jellyfin.exe`.
+
+### Running The Tests
+
+This repository also includes unit tests that are used to validate functionality as part of a CI pipeline on Azure. There are several ways to run these tests.
+
+1. Run tests from the command line using `dotnet test`
+2. Run tests in Visual Studio using the [Test Explorer](https://docs.microsoft.com/en-us/visualstudio/test/run-unit-tests-with-test-explorer)
+3. Run individual tests in Visual Studio Code using the associated [CodeLens annotation](https://github.com/OmniSharp/omnisharp-vscode/wiki/How-to-run-and-debug-unit-tests)
+
+### Advanced Configuration
+
+The following sections describe some more advanced scenarios for running the server from source that build upon the standard instructions above.
+
+#### Hosting The Web Client Separately
+
+It is not necessary to host the frontend web client as part of the backend server. Hosting these two components separately may be useful for frontend developers who would prefer to host the client in a separate webpack development server for a tighter development loop. See the [jellyfin-web](https://github.com/jellyfin/jellyfin-web#getting-started) repo for instructions on how to do this.
+
+To instruct the server not to host the web content, there is a `nowebcontent` configuration flag that must be set. This can specified using the command line switch `--nowebcontent` or the environment variable `JELLYFIN_NOWEBCONTENT=true`.
+
+Since this is a common scenario, there is also a separate launch profile defined for Visual Studio called `Jellyfin.Server (nowebcontent)` that can be selected from the 'Start Debugging' dropdown in the main toolbar.
diff --git a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
index a2ef2dcd6..49cb2387b 100644
--- a/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/CleanDateTimeTests.cs
@@ -39,11 +39,11 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData(@"[rec].mkv", "[rec].mkv", null)]
[InlineData(@"St. Vincent (2014)", "St. Vincent", 2014)]
[InlineData("Super movie(2009).mp4", "Super movie", 2009)]
- // FIXME: [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
+ [InlineData("Drug War 2013.mp4", "Drug War", 2013)]
[InlineData("My Movie (1997) - GreatestReleaseGroup 2019.mp4", "My Movie", 1997)]
- // FIXME: [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
+ [InlineData("First Man 2018 1080p.mkv", "First Man", 2018)]
[InlineData("First Man (2018) 1080p.mkv", "First Man", 2018)]
- // FIXME: [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
+ [InlineData("Maximum Ride - 2016 - WEBDL-1080p - x264 AC3.mkv", "Maximum Ride", 2016)]
// FIXME: [InlineData("Robin Hood [Multi-Subs] [2018].mkv", "Robin Hood", 2018)]
[InlineData(@"3.Days.to.Kill.2014.720p.BluRay.x264.YIFY.mkv", "3.Days.to.Kill", 2014)] // In this test case, running CleanDateTime first produces no date, so it will attempt to run CleanString first and then CleanDateTime again
public void CleanDateTimeTest(string input, string expectedName, int? expectedYear)