aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/azure-pipelines-package.yml42
-rw-r--r--.ci/azure-pipelines.yml4
-rw-r--r--Emby.Dlna/Api/DlnaServerService.cs7
-rw-r--r--Emby.Dlna/Profiles/DefaultProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DenonAvrProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/DirectTvProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/Foobar2000Profile.cs2
-rw-r--r--Emby.Dlna/Profiles/MarantzProfile.cs2
-rw-r--r--Emby.Dlna/Profiles/MediaMonkeyProfile.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs3
-rw-r--r--Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs3
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs35
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj8
-rw-r--r--Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs13
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs10
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs47
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs9
-rw-r--r--Emby.Server.Implementations/Localization/Core/af.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/mr.json4
-rw-r--r--Emby.Server.Implementations/Localization/Core/ms.json64
-rw-r--r--Emby.Server.Implementations/Localization/Core/pt.json11
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json4
-rw-r--r--Emby.Server.Implementations/Networking/NetworkManager.cs13
-rw-r--r--Emby.Server.Implementations/Services/ServiceController.cs1
-rw-r--r--Emby.Server.Implementations/Services/ServiceHandler.cs4
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs2
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayController.cs32
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs10
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs29
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs5
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj2
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj5
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj4
-rw-r--r--Jellyfin.Server.Implementations/JellyfinDb.cs13
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs98
-rw-r--r--Jellyfin.Server/CoreAppHost.cs8
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj4
-rw-r--r--Jellyfin.Server/Migrations/IMigrationRoutine.cs5
-rw-r--r--Jellyfin.Server/Migrations/MigrationRunner.cs6
-rw-r--r--Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs3
-rw-r--r--Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs49
-rw-r--r--Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs3
-rw-r--r--Jellyfin.Server/Program.cs4
-rw-r--r--MediaBrowser.Api/FilterService.cs2
-rw-r--r--MediaBrowser.Api/Images/ImageService.cs5
-rw-r--r--MediaBrowser.Api/SyncPlay/SyncPlayService.cs79
-rw-r--r--MediaBrowser.Api/System/ActivityLogService.cs5
-rw-r--r--MediaBrowser.Common/Extensions/HttpContextExtensions.cs33
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj4
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs2
-rw-r--r--MediaBrowser.Controller/Library/TVUtils.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj4
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs397
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupInfo.cs22
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupMember.cs2
-rw-r--r--MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs2
-rw-r--r--MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs1
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs4
-rw-r--r--MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs2
-rw-r--r--MediaBrowser.Model/Dlna/StreamInfo.cs14
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs3
-rw-r--r--MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs6
-rw-r--r--MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs6
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs4
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs260
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj4
-rw-r--r--MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html42
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html62
-rw-r--r--MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html35
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs40
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs4
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs5
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs15
-rw-r--r--MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs3
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs12
-rw-r--r--debian/changelog6
-rw-r--r--debian/control5
-rwxr-xr-xdeployment/build.windows.amd6410
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj6
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs12
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj2
-rw-r--r--windows/build-jellyfin.ps12
94 files changed, 1095 insertions, 648 deletions
diff --git a/.ci/azure-pipelines-package.yml b/.ci/azure-pipelines-package.yml
index b34253190..272111299 100644
--- a/.ci/azure-pipelines-package.yml
+++ b/.ci/azure-pipelines-package.yml
@@ -35,7 +35,6 @@ jobs:
steps:
- script: 'docker build -f deployment/Dockerfile.$(BuildConfiguration) -t jellyfin-server-$(BuildConfiguration) deployment'
displayName: 'Build Dockerfile'
- condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
- script: 'docker image ls -a && docker run -v $(pwd)/deployment/dist:/dist -v $(pwd):/jellyfin -e IS_UNSTABLE="yes" -e BUILD_ID=$(Build.BuildNumber) jellyfin-server-$(BuildConfiguration)'
displayName: 'Run Dockerfile (unstable)'
@@ -47,14 +46,19 @@ jobs:
- task: PublishPipelineArtifact@1
displayName: 'Publish Release'
- condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
targetPath: '$(Build.SourcesDirectory)/deployment/dist'
artifactName: 'jellyfin-server-$(BuildConfiguration)'
+ - task: SSH@0
+ displayName: 'Create target directory on repository server'
+ inputs:
+ sshEndpoint: repository
+ runOptions: 'inline'
+ inline: 'mkdir -p /srv/repository/incoming/azure/$(Build.BuildNumber)/$(BuildConfiguration)'
+
- task: CopyFilesOverSSH@0
displayName: 'Upload artifacts to repository server'
- condition: or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))
inputs:
sshEndpoint: repository
sourceFolder: '$(Build.SourcesDirectory)/deployment/dist'
@@ -120,7 +124,10 @@ jobs:
inputs:
sshEndpoint: repository
runOptions: 'inline'
- inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber) unstable
+ rm $0
+ exit
- task: SSH@0
displayName: 'Update Stable Repository'
@@ -128,4 +135,29 @@ jobs:
inputs:
sshEndpoint: repository
runOptions: 'inline'
- inline: 'sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)'
+ inline: |
+ sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
+ rm $0
+ exit
+
+- job: PublishNuget
+ displayName: 'Publish NuGet packages'
+ dependsOn:
+ - BuildPackage
+ condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+ - task: NuGetCommand@2
+ inputs:
+ command: 'pack'
+ packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
+ packDestination: '$(Build.ArtifactStagingDirectory)'
+
+ - task: NuGetCommand@2
+ inputs:
+ command: 'push'
+ packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
+ includeNugetOrg: 'true'
diff --git a/.ci/azure-pipelines.yml b/.ci/azure-pipelines.yml
index c9013b3b8..0c86c0171 100644
--- a/.ci/azure-pipelines.yml
+++ b/.ci/azure-pipelines.yml
@@ -15,11 +15,13 @@ trigger:
batch: true
jobs:
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-main.yml
parameters:
LinuxImage: 'ubuntu-latest'
RestoreBuildProjects: $(RestoreBuildProjects)
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-test.yml
parameters:
ImageNames:
@@ -27,6 +29,7 @@ jobs:
Windows: 'windows-latest'
macOS: 'macos-latest'
+- ${{ if not(or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master'))) }}:
- template: azure-pipelines-abi.yml
parameters:
Packages:
@@ -44,4 +47,5 @@ jobs:
AssemblyFileName: MediaBrowser.Common.dll
LinuxImage: 'ubuntu-latest'
+- ${{ if or(startsWith(variables['Build.SourceBranch'], 'refs/tags'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')) }}:
- template: azure-pipelines-package.yml
diff --git a/Emby.Dlna/Api/DlnaServerService.cs b/Emby.Dlna/Api/DlnaServerService.cs
index 7fba2184a..d9c1669b0 100644
--- a/Emby.Dlna/Api/DlnaServerService.cs
+++ b/Emby.Dlna/Api/DlnaServerService.cs
@@ -11,6 +11,7 @@ using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
namespace Emby.Dlna.Api
{
@@ -108,7 +109,7 @@ namespace Emby.Dlna.Api
public string Filename { get; set; }
}
- public class DlnaServerService : IService, IRequiresRequest
+ public class DlnaServerService : IService
{
private const string XMLContentType = "text/xml; charset=UTF-8";
@@ -127,11 +128,13 @@ namespace Emby.Dlna.Api
public DlnaServerService(
IDlnaManager dlnaManager,
IHttpResultFactory httpResultFactory,
- IServerConfigurationManager configurationManager)
+ IServerConfigurationManager configurationManager,
+ IHttpContextAccessor httpContextAccessor)
{
_dlnaManager = dlnaManager;
_resultFactory = httpResultFactory;
_configurationManager = configurationManager;
+ Request = httpContextAccessor?.HttpContext.GetServiceStackRequest() ?? throw new ArgumentNullException(nameof(httpContextAccessor));
}
private string GetHeader(string name)
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index 2347ebd0d..90a23a4a2 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -164,7 +164,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value)
{
- var atts = XmlRootAttributes ?? new XmlAttribute[] { };
+ var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
var list = atts.ToList();
list.Add(new XmlAttribute
diff --git a/Emby.Dlna/Profiles/DenonAvrProfile.cs b/Emby.Dlna/Profiles/DenonAvrProfile.cs
index 73a87c499..a5ba0f36c 100644
--- a/Emby.Dlna/Profiles/DenonAvrProfile.cs
+++ b/Emby.Dlna/Profiles/DenonAvrProfile.cs
@@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/DirectTvProfile.cs b/Emby.Dlna/Profiles/DirectTvProfile.cs
index 5ca388167..f6f98b07d 100644
--- a/Emby.Dlna/Profiles/DirectTvProfile.cs
+++ b/Emby.Dlna/Profiles/DirectTvProfile.cs
@@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/Foobar2000Profile.cs b/Emby.Dlna/Profiles/Foobar2000Profile.cs
index ea3de686a..68e959770 100644
--- a/Emby.Dlna/Profiles/Foobar2000Profile.cs
+++ b/Emby.Dlna/Profiles/Foobar2000Profile.cs
@@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/MarantzProfile.cs b/Emby.Dlna/Profiles/MarantzProfile.cs
index 6cfcc3b82..24078ab29 100644
--- a/Emby.Dlna/Profiles/MarantzProfile.cs
+++ b/Emby.Dlna/Profiles/MarantzProfile.cs
@@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles
},
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = System.Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
index 7161af738..28d639b6d 100644
--- a/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
+++ b/Emby.Dlna/Profiles/MediaMonkeyProfile.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -37,7 +38,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
index 42b066d52..238fe9f6b 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2013.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
index fbdf2c18e..812a48151 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2014.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -223,7 +224,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
index ce32179a1..6bfff322e 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2015.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
index aa1721d39..ec2529574 100644
--- a/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ b/Emby.Dlna/Profiles/SonyBlurayPlayer2016.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.Profiles
@@ -211,7 +212,7 @@ namespace Emby.Dlna.Profiles
}
};
- ResponseProfiles = new ResponseProfile[] { };
+ ResponseProfiles = Array.Empty<ResponseProfile>();
}
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 18b6834ef..8e1bf9766 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -43,9 +43,9 @@ using Emby.Server.Implementations.Security;
using Emby.Server.Implementations.Serialization;
using Emby.Server.Implementations.Services;
using Emby.Server.Implementations.Session;
+using Emby.Server.Implementations.SyncPlay;
using Emby.Server.Implementations.TV;
using Emby.Server.Implementations.Updates;
-using Emby.Server.Implementations.SyncPlay;
using MediaBrowser.Api;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
@@ -78,8 +78,8 @@ using MediaBrowser.Controller.Security;
using MediaBrowser.Controller.Session;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Controller.Subtitles;
-using MediaBrowser.Controller.TV;
using MediaBrowser.Controller.SyncPlay;
+using MediaBrowser.Controller.TV;
using MediaBrowser.LocalMetadata.Savers;
using MediaBrowser.MediaEncoding.BdInfo;
using MediaBrowser.Model.Configuration;
@@ -192,7 +192,7 @@ namespace Emby.Server.Implementations
/// Gets or sets the application paths.
/// </summary>
/// <value>The application paths.</value>
- protected ServerApplicationPaths ApplicationPaths { get; set; }
+ protected IServerApplicationPaths ApplicationPaths { get; set; }
/// <summary>
/// Gets or sets all concrete types.
@@ -236,7 +236,7 @@ namespace Emby.Server.Implementations
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
/// </summary>
public ApplicationHost(
- ServerApplicationPaths applicationPaths,
+ IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
IStartupOptions options,
IFileSystem fileSystem,
@@ -484,12 +484,10 @@ namespace Emby.Server.Implementations
foreach (var plugin in Plugins)
{
- pluginBuilder.AppendLine(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0} {1}",
- plugin.Name,
- plugin.Version));
+ pluginBuilder.Append(plugin.Name)
+ .Append(' ')
+ .Append(plugin.Version)
+ .AppendLine();
}
Logger.LogInformation("Plugins: {Plugins}", pluginBuilder.ToString());
@@ -871,6 +869,11 @@ namespace Emby.Server.Implementations
Logger.LogError(ex, "Error getting exported types from {Assembly}", ass.FullName);
continue;
}
+ catch (TypeLoadException ex)
+ {
+ Logger.LogError(ex, "Error loading types from {Assembly}.", ass.FullName);
+ continue;
+ }
foreach (Type type in exportedTypes)
{
@@ -1153,7 +1156,7 @@ namespace Emby.Server.Implementations
return null;
}
- return GetLocalApiUrl(addresses.First());
+ return GetLocalApiUrl(addresses[0]);
}
catch (Exception ex)
{
@@ -1226,7 +1229,7 @@ namespace Emby.Server.Implementations
var addresses = ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
- .Select(NormalizeConfiguredLocalAddress)
+ .Select(x => NormalizeConfiguredLocalAddress(x))
.Where(i => i != null)
.ToList();
@@ -1247,8 +1250,7 @@ namespace Emby.Server.Implementations
}
}
- var valid = await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false);
- if (valid)
+ if (await IsLocalIpAddressValidAsync(address, cancellationToken).ConfigureAwait(false))
{
resultList.Add(address);
@@ -1262,13 +1264,12 @@ namespace Emby.Server.Implementations
return resultList;
}
- public IPAddress NormalizeConfiguredLocalAddress(string address)
+ public IPAddress NormalizeConfiguredLocalAddress(ReadOnlySpan<char> address)
{
var index = address.Trim('/').IndexOf('/');
-
if (index != -1)
{
- address = address.Substring(index + 1);
+ address = address.Slice(index + 1);
}
if (IPAddress.TryParse(address.Trim('/'), out IPAddress result))
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index f7ad59c10..4351b9aa5 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -34,10 +34,10 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
<PackageReference Include="Mono.Nat" Version="2.0.1" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
diff --git a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
index b207397bd..9486874d5 100644
--- a/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
+++ b/Emby.Server.Implementations/EntryPoints/UdpServerEntryPoint.cs
@@ -1,3 +1,4 @@
+using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Udp;
@@ -48,8 +49,16 @@ namespace Emby.Server.Implementations.EntryPoints
/// <inheritdoc />
public Task RunAsync()
{
- _udpServer = new UdpServer(_logger, _appHost, _config);
- _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ try
+ {
+ _udpServer = new UdpServer(_logger, _appHost, _config);
+ _udpServer.Start(PortNumber, _cancellationTokenSource.Token);
+ }
+ catch (SocketException ex)
+ {
+ _logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
+ }
+
return Task.CompletedTask;
}
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index a3a3f91b7..ab6483bf9 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -245,6 +245,16 @@ namespace Emby.Server.Implementations.IO
if (info is FileInfo fileInfo)
{
result.Length = fileInfo.Length;
+
+ // Issue #2354 get the size of files behind symbolic links
+ if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
+ {
+ using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
+ {
+ result.Length = thisFileStream.Length;
+ }
+ }
+
result.DirectoryName = fileInfo.DirectoryName;
}
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 77d44e131..51c19fde7 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -341,7 +341,7 @@ namespace Emby.Server.Implementations.Library
if (item is LiveTvProgram)
{
_logger.LogDebug(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -350,7 +350,7 @@ namespace Emby.Server.Implementations.Library
else
{
_logger.LogInformation(
- "Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ "Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
item.GetType().Name,
item.Name ?? "Unknown name",
item.Path ?? string.Empty,
@@ -368,7 +368,12 @@ namespace Emby.Server.Implementations.Library
continue;
}
- _logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
+ _logger.LogDebug(
+ "Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ metadataPath,
+ item.Id);
try
{
@@ -392,7 +397,13 @@ namespace Emby.Server.Implementations.Library
{
try
{
- _logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
+ _logger.LogInformation(
+ "Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
+ item.GetType().Name,
+ item.Name ?? "Unknown name",
+ fileSystemInfo.FullName,
+ item.Id);
+
if (fileSystemInfo.IsDirectory)
{
Directory.Delete(fileSystemInfo.FullName, true);
@@ -1865,7 +1876,8 @@ namespace Emby.Server.Implementations.Library
}
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
- if (outdated.Length == 0)
+ // Skip image processing if current or live tv source
+ if (outdated.Length == 0 || item.SourceType != SourceType.Library)
{
RegisterItem(item);
return;
@@ -3000,21 +3012,6 @@ namespace Emby.Server.Implementations.Library
});
}
- private static bool ValidateNetworkPath(string path)
- {
- // if (Environment.OSVersion.Platform == PlatformID.Win32NT)
- //{
- // // We can't validate protocol-based paths, so just allow them
- // if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
- // {
- // return Directory.Exists(path);
- // }
- //}
-
- // Without native support for unc, we cannot validate this when running under mono
- return true;
- }
-
private const string ShortcutFileExtension = ".mblink";
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
@@ -3041,11 +3038,6 @@ namespace Emby.Server.Implementations.Library
throw new FileNotFoundException("The path does not exist.");
}
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
- }
-
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
@@ -3084,11 +3076,6 @@ namespace Emby.Server.Implementations.Library
throw new ArgumentNullException(nameof(pathInfo));
}
- if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
- {
- throw new FileNotFoundException("The network path does not exist.");
- }
-
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 4c1de3bcc..1b075d86a 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
+using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.Library;
@@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Querying;
-using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
@@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv
private readonly ILibraryManager _libraryManager;
private readonly ITaskManager _taskManager;
private readonly ILocalizationManager _localization;
- private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private readonly IChannelManager _channelManager;
private readonly LiveTvDtoService _tvDtoService;
@@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv
ILibraryManager libraryManager,
ITaskManager taskManager,
ILocalizationManager localization,
- IJsonSerializer jsonSerializer,
IFileSystem fileSystem,
IChannelManager channelManager,
LiveTvDtoService liveTvDtoService)
@@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv
_libraryManager = libraryManager;
_taskManager = taskManager;
_localization = localization;
- _jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_dtoService = dtoService;
_userDataManager = userDataManager;
@@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
{
- info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info));
+ info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info));
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
@@ -2278,7 +2275,7 @@ namespace Emby.Server.Implementations.LiveTv
{
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
- info = _jsonSerializer.DeserializeFromString<ListingsProviderInfo>(_jsonSerializer.SerializeToString(info));
+ info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info));
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
diff --git a/Emby.Server.Implementations/Localization/Core/af.json b/Emby.Server.Implementations/Localization/Core/af.json
index 20447347b..e587c37d5 100644
--- a/Emby.Server.Implementations/Localization/Core/af.json
+++ b/Emby.Server.Implementations/Localization/Core/af.json
@@ -19,8 +19,8 @@
"Sync": "Sinkroniseer",
"HeaderFavoriteSongs": "Gunsteling Liedjies",
"Songs": "Liedjies",
- "DeviceOnlineWithName": "{0} is verbind",
- "DeviceOfflineWithName": "{0} het afgesluit",
+ "DeviceOnlineWithName": "{0} gekoppel is",
+ "DeviceOfflineWithName": "{0} is ontkoppel",
"Collections": "Versamelings",
"Inherit": "Ontvang",
"HeaderLiveTV": "Live TV",
@@ -91,5 +91,9 @@
"ChapterNameValue": "Hoofstuk",
"CameraImageUploadedFrom": "'n Nuwe kamera photo opgelaai van {0}",
"AuthenticationSucceededWithUserName": "{0} suksesvol geverifieer",
- "Albums": "Albums"
+ "Albums": "Albums",
+ "TasksChannelsCategory": "Internet kanale",
+ "TasksApplicationCategory": "aansoek",
+ "TasksLibraryCategory": "biblioteek",
+ "TasksMaintenanceCategory": "onderhoud"
}
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index 82df43be1..a6e9779c9 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 authentifiziert",
+ "AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
"Books": "Bücher",
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
"Channels": "Kanäle",
diff --git a/Emby.Server.Implementations/Localization/Core/mr.json b/Emby.Server.Implementations/Localization/Core/mr.json
index 50b6360d8..b6db2b0f2 100644
--- a/Emby.Server.Implementations/Localization/Core/mr.json
+++ b/Emby.Server.Implementations/Localization/Core/mr.json
@@ -57,5 +57,7 @@
"HeaderCameraUploads": "कॅमेरा अपलोड",
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
"Application": "अ‍ॅप्लिकेशन",
- "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}"
+ "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}",
+ "Collections": "संग्रह",
+ "ChapterNameValue": "धडा {0}"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ms.json b/Emby.Server.Implementations/Localization/Core/ms.json
index 79d078d4a..7f8df1289 100644
--- a/Emby.Server.Implementations/Localization/Core/ms.json
+++ b/Emby.Server.Implementations/Localization/Core/ms.json
@@ -5,47 +5,47 @@
"Artists": "Artis",
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
"Books": "Buku-buku",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
+ "CameraImageUploadedFrom": "Ada gambar dari kamera yang baru dimuat naik melalui {0}",
"Channels": "Saluran",
- "ChapterNameValue": "Chapter {0}",
+ "ChapterNameValue": "Bab {0}",
"Collections": "Koleksi",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
+ "DeviceOfflineWithName": "{0} telah diputuskan sambungan",
+ "DeviceOnlineWithName": "{0} telah disambung",
"FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
+ "Favorites": "Kegemaran",
+ "Folders": "Fail-fail",
"Genres": "Genre-genre",
- "HeaderAlbumArtists": "Album Artists",
+ "HeaderAlbumArtists": "Album Artis-artis",
"HeaderCameraUploads": "Muatnaik Kamera",
"HeaderContinueWatching": "Terus Menonton",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
- "HeaderFavoriteEpisodes": "Favorite Episodes",
- "HeaderFavoriteShows": "Favorite Shows",
- "HeaderFavoriteSongs": "Favorite Songs",
- "HeaderLiveTV": "Live TV",
- "HeaderNextUp": "Next Up",
- "HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
- "Inherit": "Inherit",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
+ "HeaderFavoriteAlbums": "Album-album Kegemaran",
+ "HeaderFavoriteArtists": "Artis-artis Kegemaran",
+ "HeaderFavoriteEpisodes": "Episod-episod Kegemaran",
+ "HeaderFavoriteShows": "Rancangan-rancangan Kegemaran",
+ "HeaderFavoriteSongs": "Lagu-lagu Kegemaran",
+ "HeaderLiveTV": "TV Siaran Langsung",
+ "HeaderNextUp": "Seterusnya",
+ "HeaderRecordingGroups": "Kumpulan-kumpulan Rakaman",
+ "HomeVideos": "Video Personal",
+ "Inherit": "Mewarisi",
+ "ItemAddedWithName": "{0} telah ditambahkan ke dalam pustaka",
+ "ItemRemovedWithName": "{0} telah dibuang daripada pustaka",
"LabelIpAddressValue": "Alamat IP: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
- "Latest": "Latest",
- "MessageApplicationUpdated": "Jellyfin Server has been updated",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MixedContent": "Mixed content",
- "Movies": "Movies",
+ "LabelRunningTimeValue": "Masa berjalan: {0}",
+ "Latest": "Terbaru",
+ "MessageApplicationUpdated": "Jellyfin Server telah dikemas kini",
+ "MessageApplicationUpdatedTo": "Jellyfin Server telah dikemas kini ke {0}",
+ "MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
+ "MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
+ "MixedContent": "Kandungan campuran",
+ "Movies": "Filem",
"Music": "Muzik",
"MusicVideos": "Video muzik",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
+ "NameInstallFailed": "{0} pemasangan gagal",
+ "NameSeasonNumber": "Musim {0}",
+ "NameSeasonUnknown": "Musim Tidak Diketahui",
+ "NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.",
+ "NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia",
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
"NotificationOptionAudioPlayback": "Audio playback started",
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
diff --git a/Emby.Server.Implementations/Localization/Core/pt.json b/Emby.Server.Implementations/Localization/Core/pt.json
index 5365fff23..b534d0bbe 100644
--- a/Emby.Server.Implementations/Localization/Core/pt.json
+++ b/Emby.Server.Implementations/Localization/Core/pt.json
@@ -104,5 +104,14 @@
"TaskRefreshChapterImagesDescription": "Cria miniaturas para vídeos que têm capítulos.",
"TaskCleanCacheDescription": "Apaga ficheiros em cache que já não são usados pelo sistema.",
"TasksChannelsCategory": "Canais de Internet",
- "TaskRefreshChapterImages": "Extrair Imagens do Capítulo"
+ "TaskRefreshChapterImages": "Extrair Imagens do Capítulo",
+ "TaskDownloadMissingSubtitlesDescription": "Pesquisa na Internet as legendas em falta com base na configuração de metadados.",
+ "TaskDownloadMissingSubtitles": "Download das legendas em falta",
+ "TaskRefreshChannelsDescription": "Atualiza as informações do canal da Internet.",
+ "TaskCleanTranscodeDescription": "Apagar os ficheiros com mais de um dia, de Transcode.",
+ "TaskCleanTranscode": "Limpar o diretório de Transcode",
+ "TaskUpdatePluginsDescription": "Download e instala as atualizações para plug-ins configurados para atualização automática.",
+ "TaskRefreshPeopleDescription": "Atualiza os metadados para atores e diretores na tua biblioteca de media.",
+ "TaskRefreshPeople": "Atualizar pessoas",
+ "TaskRefreshLibraryDescription": "Pesquisa a tua biblioteca de media por novos ficheiros e atualiza os metadados."
}
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index 32538ac03..576aaeb1b 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -67,5 +67,7 @@
"Artists": "นักแสดง",
"Application": "แอปพลิเคชั่น",
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
- "Albums": "อัลบั้ม"
+ "Albums": "อัลบั้ม",
+ "ScheduledTaskStartedWithName": "{0} เริ่มต้น",
+ "ScheduledTaskFailedWithName": "{0} ล้มเหลว"
}
diff --git a/Emby.Server.Implementations/Networking/NetworkManager.cs b/Emby.Server.Implementations/Networking/NetworkManager.cs
index 6aa1dfbc9..50cb44b28 100644
--- a/Emby.Server.Implementations/Networking/NetworkManager.cs
+++ b/Emby.Server.Implementations/Networking/NetworkManager.cs
@@ -152,7 +152,12 @@ namespace Emby.Server.Implementations.Networking
return true;
}
- byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes();
+ if (!IPAddress.TryParse(endpoint, out var ipAddress))
+ {
+ return false;
+ }
+
+ byte[] octet = ipAddress.GetAddressBytes();
if ((octet[0] == 10) ||
(octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
@@ -268,6 +273,12 @@ namespace Emby.Server.Implementations.Networking
string excludeAddress = "[" + addressString + "]";
var subnets = LocalSubnetsFn();
+ // Include any address if LAN subnets aren't specified
+ if (subnets.Length == 0)
+ {
+ return true;
+ }
+
// Exclude any addresses if they appear in the LAN list in [ ]
if (Array.IndexOf(subnets, excludeAddress) != -1)
{
diff --git a/Emby.Server.Implementations/Services/ServiceController.cs b/Emby.Server.Implementations/Services/ServiceController.cs
index 857df591a..d884d4f37 100644
--- a/Emby.Server.Implementations/Services/ServiceController.cs
+++ b/Emby.Server.Implementations/Services/ServiceController.cs
@@ -189,5 +189,4 @@ namespace Emby.Server.Implementations.Services
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
}
}
-
}
diff --git a/Emby.Server.Implementations/Services/ServiceHandler.cs b/Emby.Server.Implementations/Services/ServiceHandler.cs
index a42f88ea0..3d4e1ca77 100644
--- a/Emby.Server.Implementations/Services/ServiceHandler.cs
+++ b/Emby.Server.Implementations/Services/ServiceHandler.cs
@@ -6,6 +6,7 @@ using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Emby.Server.Implementations.HttpServer;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Model.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@@ -78,7 +79,8 @@ namespace Emby.Server.Implementations.Services
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
-
+
+ httpRes.HttpContext.SetServiceStackRequest(httpReq);
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
// Apply response filters
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index d069d1ada..ca9f95c70 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -296,7 +296,7 @@ namespace Emby.Server.Implementations.Session
}
catch (DbUpdateConcurrencyException e)
{
- _logger.LogWarning(e, "Error updating user's last activity date.");
+ _logger.LogDebug(e, "Error updating user's last activity date.");
}
}
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
index b1f8fd330..39d17833f 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayController.cs
@@ -194,26 +194,24 @@ namespace Emby.Server.Implementations.SyncPlay
}
/// <inheritdoc />
- public void InitGroup(SessionInfo session, CancellationToken cancellationToken)
+ public void CreateGroup(SessionInfo session, CancellationToken cancellationToken)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
_group.PlayingItem = session.FullNowPlayingItem;
- _group.IsPaused = true;
+ _group.IsPaused = session.PlayState.IsPaused;
_group.PositionTicks = session.PlayState.PositionTicks ?? 0;
_group.LastActivity = DateTime.UtcNow;
var updateSession = NewSyncPlayGroupUpdate(GroupUpdateType.GroupJoined, DateToUTCString(DateTime.UtcNow));
SendGroupUpdate(session, BroadcastType.CurrentSession, updateSession, cancellationToken);
- var pauseCommand = NewSyncPlayCommand(SendCommandType.Pause);
- SendCommand(session, BroadcastType.CurrentSession, pauseCommand, cancellationToken);
}
/// <inheritdoc />
public void SessionJoin(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
{
- if (session.NowPlayingItem?.Id == _group.PlayingItem.Id && request.PlayingItemId == _group.PlayingItem.Id)
+ if (session.NowPlayingItem?.Id == _group.PlayingItem.Id)
{
_group.AddSession(session);
_syncPlayManager.AddSessionToGroup(session, this);
@@ -224,7 +222,7 @@ namespace Emby.Server.Implementations.SyncPlay
var updateOthers = NewSyncPlayGroupUpdate(GroupUpdateType.UserJoined, session.UserName);
SendGroupUpdate(session, BroadcastType.AllExceptCurrentSession, updateOthers, cancellationToken);
- // Client join and play, syncing will happen client side
+ // Syncing will happen client-side
if (!_group.IsPaused)
{
var playCommand = NewSyncPlayCommand(SendCommandType.Play);
@@ -262,10 +260,9 @@ namespace Emby.Server.Implementations.SyncPlay
/// <inheritdoc />
public void HandleRequest(SessionInfo session, PlaybackRequest request, CancellationToken cancellationToken)
{
- // The server's job is to mantain a consistent state to which clients refer to,
- // as also to notify clients of state changes.
- // The actual syncing of media playback happens client side.
- // Clients are aware of the server's time and use it to sync.
+ // The server's job is to maintain a consistent state for clients to reference
+ // and notify clients of state changes. The actual syncing of media playback
+ // happens client side. Clients are aware of the server's time and use it to sync.
switch (request.Type)
{
case PlaybackRequestType.Play:
@@ -277,13 +274,13 @@ namespace Emby.Server.Implementations.SyncPlay
case PlaybackRequestType.Seek:
HandleSeekRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.Buffering:
+ case PlaybackRequestType.Buffer:
HandleBufferingRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.BufferingDone:
+ case PlaybackRequestType.Ready:
HandleBufferingDoneRequest(session, request, cancellationToken);
break;
- case PlaybackRequestType.UpdatePing:
+ case PlaybackRequestType.Ping:
HandlePingUpdateRequest(session, request);
break;
}
@@ -301,7 +298,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Pick a suitable time that accounts for latency
var delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
// Unpause group and set starting point in future
// Clients will start playback at LastActivity (datetime) from PositionTicks (playback position)
@@ -337,8 +334,9 @@ namespace Emby.Server.Implementations.SyncPlay
var currentTime = DateTime.UtcNow;
var elapsedTime = currentTime - _group.LastActivity;
_group.LastActivity = currentTime;
+
// Seek only if playback actually started
- // (a pause request may be issued during the delay added to account for latency)
+ // Pause request may be issued during the delay added to account for latency
_group.PositionTicks += elapsedTime.Ticks > 0 ? elapsedTime.Ticks : 0;
var command = NewSyncPlayCommand(SendCommandType.Pause);
@@ -451,7 +449,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
// Client, that was buffering, resumed playback but did not update others in time
delay = _group.GetHighestPing() * 2;
- delay = delay < _group.DefaulPing ? _group.DefaulPing : delay;
+ delay = delay < _group.DefaultPing ? _group.DefaultPing : delay;
_group.LastActivity = currentTime.AddMilliseconds(
delay);
@@ -495,7 +493,7 @@ namespace Emby.Server.Implementations.SyncPlay
private void HandlePingUpdateRequest(SessionInfo session, PlaybackRequest request)
{
// Collected pings are used to account for network latency when unpausing playback
- _group.UpdatePing(session, request.Ping ?? _group.DefaulPing);
+ _group.UpdatePing(session, request.Ping ?? _group.DefaultPing);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 45a43fd78..966ed5024 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -170,10 +170,11 @@ namespace Emby.Server.Implementations.SyncPlay
{
_logger.LogWarning("NewGroup: {0} does not have permission to create groups.", session.Id);
- var error = new GroupUpdate<string>()
+ var error = new GroupUpdate<string>
{
Type = GroupUpdateType.CreateGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -188,7 +189,7 @@ namespace Emby.Server.Implementations.SyncPlay
var group = new SyncPlayController(_sessionManager, this);
_groups[group.GetGroupId()] = group;
- group.InitGroup(session, cancellationToken);
+ group.CreateGroup(session, cancellationToken);
}
}
@@ -205,6 +206,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -300,9 +302,9 @@ namespace Emby.Server.Implementations.SyncPlay
group => group.GetPlayingItemId().Equals(filterItemId) && HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
}
- // Otherwise show all available groups
else
{
+ // Otherwise show all available groups
return _groups.Values.Where(
group => HasAccessToItem(user, group.GetPlayingItemId())).Select(
group => group.GetInfo()).ToList();
@@ -322,6 +324,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
Type = GroupUpdateType.JoinGroupDenied
};
+
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
return;
}
@@ -366,7 +369,6 @@ namespace Emby.Server.Implementations.SyncPlay
}
_sessionToGroupMap.Remove(session.Id, out var tempGroup);
-
if (!tempGroup.GetGroupId().Equals(group.GetGroupId()))
{
throw new InvalidOperationException("Session was in wrong group!");
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index 21c12ae79..d1818deff 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -117,23 +117,20 @@ namespace Emby.Server.Implementations.TV
limit = limit.Value + 10;
}
- var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
- {
- IncludeItemTypes = new[] { typeof(Episode).Name },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
- SeriesPresentationUniqueKey = presentationUniqueKey,
- Limit = limit,
- DtoOptions = new DtoOptions
- {
- Fields = new ItemFields[]
+ var items = _libraryManager
+ .GetItemList(
+ new InternalItemsQuery(user)
{
- ItemFields.SeriesPresentationUniqueKey
- },
- EnableImages = false
- },
- GroupBySeriesPresentationUniqueKey = true
-
- }, parentsFolders.ToList()).Cast<Episode>().Select(GetUniqueSeriesKey);
+ IncludeItemTypes = new[] { typeof(Episode).Name },
+ OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
+ SeriesPresentationUniqueKey = presentationUniqueKey,
+ Limit = limit,
+ DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
+ GroupBySeriesPresentationUniqueKey = true
+ }, parentsFolders.ToList())
+ .Cast<Episode>()
+ .Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
+ .Select(GetUniqueSeriesKey);
// Avoid implicitly captured closure
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 146ebaf25..4f54c06dd 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -148,6 +148,11 @@ namespace Emby.Server.Implementations.Updates
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
return Array.Empty<PackageInfo>();
}
+ catch (HttpRequestException ex)
+ {
+ _logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
+ return Array.Empty<PackageInfo>();
+ }
}
/// <inheritdoc />
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 93c4612b6..3693d5122 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -14,7 +14,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
- <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" />
+ <PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 282ea511c..8ce0f3848 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -19,9 +19,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
- <PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="3.1.5" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.6" />
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
</ItemGroup>
</Project>
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index dcac1b34b..21748ca19 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -24,11 +24,11 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.5">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
- <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5">
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
diff --git a/Jellyfin.Server.Implementations/JellyfinDb.cs b/Jellyfin.Server.Implementations/JellyfinDb.cs
index 53120a763..acc6eec1c 100644
--- a/Jellyfin.Server.Implementations/JellyfinDb.cs
+++ b/Jellyfin.Server.Implementations/JellyfinDb.cs
@@ -1,5 +1,6 @@
#pragma warning disable CS1591
+using System;
using System.Linq;
using Jellyfin.Data;
using Jellyfin.Data.Entities;
@@ -133,6 +134,18 @@ namespace Jellyfin.Server.Implementations
return base.SaveChanges();
}
+ /// <inheritdoc/>
+ public override void Dispose()
+ {
+ foreach (var entry in ChangeTracker.Entries())
+ {
+ entry.State = EntityState.Detached;
+ }
+
+ GC.SuppressFinalize(this);
+ base.Dispose();
+ }
+
/// <inheritdoc />
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index ace9c4af0..c5c89d37d 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -23,6 +23,7 @@ using MediaBrowser.Model.Cryptography;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Users;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Jellyfin.Server.Implementations.Users
@@ -86,10 +87,31 @@ namespace Jellyfin.Server.Implementations.Users
public event EventHandler<GenericEventArgs<User>>? OnUserLockedOut;
/// <inheritdoc/>
- public IEnumerable<User> Users => _dbProvider.CreateContext().Users;
+ public IEnumerable<User> Users
+ {
+ get
+ {
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .ToList();
+ }
+ }
/// <inheritdoc/>
- public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
+ public IEnumerable<Guid> UsersIds
+ {
+ get
+ {
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Select(user => user.Id)
+ .ToList();
+ }
+ }
/// <inheritdoc/>
public User? GetUserById(Guid id)
@@ -99,7 +121,13 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Guid can't be empty", nameof(id));
}
- return _dbProvider.CreateContext().Users.Find(id);
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .FirstOrDefault(user => user.Id == id);
}
/// <inheritdoc/>
@@ -110,9 +138,14 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Invalid username", nameof(name));
}
- // This can't use an overload with StringComparer because that would cause the query to
- // have to be evaluated client-side.
- return _dbProvider.CreateContext().Users.FirstOrDefault(u => string.Equals(u.Username, name));
+ using var dbContext = _dbProvider.CreateContext();
+ return dbContext.Users
+ .Include(user => user.Permissions)
+ .Include(user => user.Preferences)
+ .Include(user => user.AccessSchedules)
+ .Include(user => user.ProfileImage)
+ .AsEnumerable()
+ .FirstOrDefault(u => string.Equals(u.Username, name, StringComparison.OrdinalIgnoreCase));
}
/// <inheritdoc/>
@@ -133,7 +166,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("The new and old names must be different.");
}
- if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase)))
+ if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.Ordinal)))
{
throw new ArgumentException(string.Format(
CultureInfo.InvariantCulture,
@@ -150,7 +183,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void UpdateUser(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
dbContext.SaveChanges();
}
@@ -158,7 +191,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public async Task UpdateUserAsync(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ await using var dbContext = _dbProvider.CreateContext();
dbContext.Users.Update(user);
await dbContext.SaveChangesAsync().ConfigureAwait(false);
@@ -172,7 +205,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentException("Usernames can contain unicode symbols, numbers (0-9), dashes (-), underscores (_), apostrophes ('), and periods (.)");
}
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
// TODO: Remove after user item data is migrated.
var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
@@ -195,8 +228,13 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void DeleteUser(Guid userId)
{
- var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId);
+ using var dbContext = _dbProvider.CreateContext();
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId);
if (user == null)
{
throw new ResourceNotFoundException(nameof(userId));
@@ -381,7 +419,7 @@ namespace Jellyfin.Server.Implementations.Users
throw new ArgumentNullException(nameof(username));
}
- var user = Users.ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
bool success;
IAuthenticationProvider? authenticationProvider;
@@ -409,8 +447,7 @@ namespace Jellyfin.Server.Implementations.Users
// Search the database for the user again
// the authentication provider might have created it
- user = Users
- .ToList().FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
+ user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
if (authenticationProvider is IHasNewUserPolicy hasNewUserPolicy)
{
@@ -484,7 +521,7 @@ namespace Jellyfin.Server.Implementations.Users
}
else
{
- IncrementInvalidLoginAttemptCount(user);
+ await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false);
_logger.LogInformation(
"Authentication request for {UserName} has been denied (IP: {IP}).",
user.Username,
@@ -547,7 +584,7 @@ namespace Jellyfin.Server.Implementations.Users
public void Initialize()
{
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
if (dbContext.Users.Any())
{
@@ -609,8 +646,15 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void UpdateConfiguration(Guid userId, UserConfiguration config)
{
- var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
+ using var dbContext = _dbProvider.CreateContext();
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId)
+ ?? throw new ArgumentException("No user exists with given Id!");
+
user.SubtitleMode = config.SubtitleMode;
user.HidePlayedInLatest = config.HidePlayedInLatest;
user.EnableLocalPassword = config.EnableLocalPassword;
@@ -635,8 +679,14 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void UpdatePolicy(Guid userId, UserPolicy policy)
{
- var dbContext = _dbProvider.CreateContext();
- var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
+ using var dbContext = _dbProvider.CreateContext();
+ var user = dbContext.Users
+ .Include(u => u.Permissions)
+ .Include(u => u.Preferences)
+ .Include(u => u.AccessSchedules)
+ .Include(u => u.ProfileImage)
+ .FirstOrDefault(u => u.Id == userId)
+ ?? throw new ArgumentException("No user exists with given Id!");
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
@@ -699,7 +749,7 @@ namespace Jellyfin.Server.Implementations.Users
/// <inheritdoc/>
public void ClearProfileImage(User user)
{
- var dbContext = _dbProvider.CreateContext();
+ using var dbContext = _dbProvider.CreateContext();
dbContext.Remove(user.ProfileImage);
dbContext.SaveChanges();
}
@@ -841,7 +891,7 @@ namespace Jellyfin.Server.Implementations.Users
}
}
- private void IncrementInvalidLoginAttemptCount(User user)
+ private async Task IncrementInvalidLoginAttemptCount(User user)
{
user.InvalidLoginAttemptCount++;
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
@@ -855,7 +905,7 @@ namespace Jellyfin.Server.Implementations.Users
user.InvalidLoginAttemptCount);
}
- UpdateUser(user);
+ await UpdateUserAsync(user).ConfigureAwait(false);
}
}
}
diff --git a/Jellyfin.Server/CoreAppHost.cs b/Jellyfin.Server/CoreAppHost.cs
index fe07411a6..71b0fd8f3 100644
--- a/Jellyfin.Server/CoreAppHost.cs
+++ b/Jellyfin.Server/CoreAppHost.cs
@@ -9,6 +9,7 @@ using Jellyfin.Server.Implementations;
using Jellyfin.Server.Implementations.Activity;
using Jellyfin.Server.Implementations.Users;
using MediaBrowser.Common.Net;
+using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
@@ -33,9 +34,9 @@ namespace Jellyfin.Server
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
public CoreAppHost(
- ServerApplicationPaths applicationPaths,
+ IServerApplicationPaths applicationPaths,
ILoggerFactory loggerFactory,
- StartupOptions options,
+ IStartupOptions options,
IFileSystem fileSystem,
INetworkManager networkManager)
: base(
@@ -66,8 +67,7 @@ namespace Jellyfin.Server
// TODO: Set up scoping and use AddDbContextPool
serviceCollection.AddDbContext<JellyfinDb>(
options => options
- .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}")
- .UseLazyLoadingProxies(),
+ .UseSqlite($"Filename={Path.Combine(ApplicationPaths.DataPath, "jellyfin.db")}"),
ServiceLifetime.Transient);
serviceCollection.AddSingleton<JellyfinDbProvider>();
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 6a2d252ab..b1bd38cff 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -41,8 +41,8 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
<PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
diff --git a/Jellyfin.Server/Migrations/IMigrationRoutine.cs b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
index 6b5780a26..c1000eede 100644
--- a/Jellyfin.Server/Migrations/IMigrationRoutine.cs
+++ b/Jellyfin.Server/Migrations/IMigrationRoutine.cs
@@ -18,6 +18,11 @@ namespace Jellyfin.Server.Migrations
public string Name { get; }
/// <summary>
+ /// Gets a value indicating whether to perform migration on a new install.
+ /// </summary>
+ public bool PerformOnNewInstall { get; }
+
+ /// <summary>
/// Execute the migration routine.
/// </summary>
public void Perform();
diff --git a/Jellyfin.Server/Migrations/MigrationRunner.cs b/Jellyfin.Server/Migrations/MigrationRunner.cs
index d633c554d..98a90500c 100644
--- a/Jellyfin.Server/Migrations/MigrationRunner.cs
+++ b/Jellyfin.Server/Migrations/MigrationRunner.cs
@@ -21,7 +21,8 @@ namespace Jellyfin.Server.Migrations
typeof(Routines.MigrateActivityLogDb),
typeof(Routines.RemoveDuplicateExtras),
typeof(Routines.AddDefaultPluginRepository),
- typeof(Routines.MigrateUserDb)
+ typeof(Routines.MigrateUserDb),
+ typeof(Routines.ReaddDefaultPluginRepository)
};
/// <summary>
@@ -43,9 +44,8 @@ namespace Jellyfin.Server.Migrations
// If startup wizard is not finished, this is a fresh install.
// Don't run any migrations, just mark all of them as applied.
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
- migrationOptions.Applied.AddRange(migrations.Select(m => (m.Id, m.Name)));
+ migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name)));
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
- return;
}
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
diff --git a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
index a9d5ad16a..f6d8c9cc0 100644
--- a/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
+++ b/Jellyfin.Server/Migrations/Routines/AddDefaultPluginRepository.cs
@@ -33,6 +33,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "AddDefaultPluginRepository";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => true;
+
+ /// <inheritdoc/>
public void Perform()
{
_serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
diff --git a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
index b15e09290..6821630db 100644
--- a/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
+++ b/Jellyfin.Server/Migrations/Routines/CreateUserLoggingConfigFile.cs
@@ -49,6 +49,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "CreateLoggingConfigHeirarchy";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var logDirectory = _appPaths.ConfigurationDirectoryPath;
diff --git a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
index c18aa1629..0925a87b5 100644
--- a/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
+++ b/Jellyfin.Server/Migrations/Routines/DisableTranscodingThrottling.cs
@@ -26,6 +26,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "DisableTranscodingThrottling";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
// Set EnableThrottling to false since it wasn't used before and may introduce issues
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
index fb3466e13..6048160c6 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateActivityLogDb.cs
@@ -42,6 +42,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "MigrateActivityLogDatabase";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var logLevelDictionary = new Dictionary<string, LogLevel>(StringComparer.OrdinalIgnoreCase)
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
index 2be10c708..274e6ab73 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs
@@ -55,6 +55,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "MigrateUserDatabase";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
diff --git a/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs
new file mode 100644
index 000000000..b281b5cc0
--- /dev/null
+++ b/Jellyfin.Server/Migrations/Routines/ReaddDefaultPluginRepository.cs
@@ -0,0 +1,49 @@
+using System;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Model.Updates;
+
+namespace Jellyfin.Server.Migrations.Routines
+{
+ /// <summary>
+ /// Migration to initialize system configuration with the default plugin repository.
+ /// </summary>
+ public class ReaddDefaultPluginRepository : IMigrationRoutine
+ {
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+
+ private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
+ {
+ Name = "Jellyfin Stable",
+ Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
+ };
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
+ /// </summary>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
+ {
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ /// <inheritdoc/>
+ public Guid Id => Guid.Parse("5F86E7F6-D966-4C77-849D-7A7B40B68C4E");
+
+ /// <inheritdoc/>
+ public string Name => "ReaddDefaultPluginRepository";
+
+ /// <inheritdoc/>
+ public bool PerformOnNewInstall => true;
+
+ /// <inheritdoc/>
+ public void Perform()
+ {
+ // Only add if repository list is empty
+ if (_serverConfigurationManager.Configuration.PluginRepositories.Count == 0)
+ {
+ _serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
+ _serverConfigurationManager.SaveConfiguration();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
index 2ebef0241..6c26e47e1 100644
--- a/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
+++ b/Jellyfin.Server/Migrations/Routines/RemoveDuplicateExtras.cs
@@ -30,6 +30,9 @@ namespace Jellyfin.Server.Migrations.Routines
public string Name => "RemoveDuplicateExtras";
/// <inheritdoc/>
+ public bool PerformOnNewInstall => false;
+
+ /// <inheritdoc/>
public void Perform()
{
var dataPath = _paths.DataPath;
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 4d898ff5e..ef3ebe90c 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -274,10 +274,10 @@ namespace Jellyfin.Server
var addresses = appHost.ServerConfigurationManager
.Configuration
.LocalNetworkAddresses
- .Select(appHost.NormalizeConfiguredLocalAddress)
+ .Select(x => appHost.NormalizeConfiguredLocalAddress(x))
.Where(i => i != null)
.ToHashSet();
- if (addresses.Any() && !addresses.Contains(IPAddress.Any))
+ if (addresses.Count > 0 && !addresses.Contains(IPAddress.Any))
{
if (!addresses.Contains(IPAddress.Loopback))
{
diff --git a/MediaBrowser.Api/FilterService.cs b/MediaBrowser.Api/FilterService.cs
index 1b736c77d..dcfdcbfed 100644
--- a/MediaBrowser.Api/FilterService.cs
+++ b/MediaBrowser.Api/FilterService.cs
@@ -125,7 +125,7 @@ namespace MediaBrowser.Api
IncludeItemTypes = request.GetIncludeItemTypes(),
DtoOptions = new Controller.Dto.DtoOptions
{
- Fields = new ItemFields[] { },
+ Fields = Array.Empty<ItemFields>(),
EnableImages = false,
EnableUserData = false
},
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs
index 8426a9a4f..575b1157a 100644
--- a/MediaBrowser.Api/Images/ImageService.cs
+++ b/MediaBrowser.Api/Images/ImageService.cs
@@ -895,6 +895,11 @@ namespace MediaBrowser.Api.Images
// Handle image/png; charset=utf-8
mimeType = mimeType.Split(';').FirstOrDefault();
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
+ if (user.ProfileImage != null)
+ {
+ _userManager.ClearProfileImage(user);
+ }
+
user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
await _providerManager
diff --git a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
index 88cddfff7..daa1b521f 100644
--- a/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
+++ b/MediaBrowser.Api/SyncPlay/SyncPlayService.cs
@@ -11,15 +11,15 @@ using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.SyncPlay
{
- [Route("/SyncPlay/NewGroup", "POST", Summary = "Create a new SyncPlay group")]
+ [Route("/SyncPlay/New", "POST", Summary = "Create a new SyncPlay group")]
[Authenticated]
- public class SyncPlayNewGroup : IReturnVoid
+ public class SyncPlayNew : IReturnVoid
{
}
- [Route("/SyncPlay/JoinGroup", "POST", Summary = "Join an existing SyncPlay group")]
+ [Route("/SyncPlay/Join", "POST", Summary = "Join an existing SyncPlay group")]
[Authenticated]
- public class SyncPlayJoinGroup : IReturnVoid
+ public class SyncPlayJoin : IReturnVoid
{
/// <summary>
/// Gets or sets the Group id.
@@ -27,24 +27,17 @@ namespace MediaBrowser.Api.SyncPlay
/// <value>The Group id to join.</value>
[ApiMember(Name = "GroupId", Description = "Group Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
public string GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- [ApiMember(Name = "PlayingItemId", Description = "Client's playing item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
- public string PlayingItemId { get; set; }
}
- [Route("/SyncPlay/LeaveGroup", "POST", Summary = "Leave joined SyncPlay group")]
+ [Route("/SyncPlay/Leave", "POST", Summary = "Leave joined SyncPlay group")]
[Authenticated]
- public class SyncPlayLeaveGroup : IReturnVoid
+ public class SyncPlayLeave : IReturnVoid
{
}
- [Route("/SyncPlay/ListGroups", "GET", Summary = "List SyncPlay groups")]
+ [Route("/SyncPlay/List", "GET", Summary = "List SyncPlay groups")]
[Authenticated]
- public class SyncPlayListGroups : IReturnVoid
+ public class SyncPlayList : IReturnVoid
{
/// <summary>
/// Gets or sets the filter item id.
@@ -54,29 +47,29 @@ namespace MediaBrowser.Api.SyncPlay
public string FilterItemId { get; set; }
}
- [Route("/SyncPlay/PlayRequest", "POST", Summary = "Request play in SyncPlay group")]
+ [Route("/SyncPlay/Play", "POST", Summary = "Request play in SyncPlay group")]
[Authenticated]
- public class SyncPlayPlayRequest : IReturnVoid
+ public class SyncPlayPlay : IReturnVoid
{
}
- [Route("/SyncPlay/PauseRequest", "POST", Summary = "Request pause in SyncPlay group")]
+ [Route("/SyncPlay/Pause", "POST", Summary = "Request pause in SyncPlay group")]
[Authenticated]
- public class SyncPlayPauseRequest : IReturnVoid
+ public class SyncPlayPause : IReturnVoid
{
}
- [Route("/SyncPlay/SeekRequest", "POST", Summary = "Request seek in SyncPlay group")]
+ [Route("/SyncPlay/Seek", "POST", Summary = "Request seek in SyncPlay group")]
[Authenticated]
- public class SyncPlaySeekRequest : IReturnVoid
+ public class SyncPlaySeek : IReturnVoid
{
[ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")]
public long PositionTicks { get; set; }
}
- [Route("/SyncPlay/BufferingRequest", "POST", Summary = "Request group wait in SyncPlay group while buffering")]
+ [Route("/SyncPlay/Buffering", "POST", Summary = "Request group wait in SyncPlay group while buffering")]
[Authenticated]
- public class SyncPlayBufferingRequest : IReturnVoid
+ public class SyncPlayBuffering : IReturnVoid
{
/// <summary>
/// Gets or sets the date used to pin PositionTicks in time.
@@ -89,16 +82,16 @@ namespace MediaBrowser.Api.SyncPlay
public long PositionTicks { get; set; }
/// <summary>
- /// Gets or sets whether this is a buffering or a buffering-done request.
+ /// Gets or sets whether this is a buffering or a ready request.
/// </summary>
/// <value><c>true</c> if buffering is complete; <c>false</c> otherwise.</value>
[ApiMember(Name = "BufferingDone", IsRequired = true, DataType = "bool", ParameterType = "query", Verb = "POST")]
public bool BufferingDone { get; set; }
}
- [Route("/SyncPlay/UpdatePing", "POST", Summary = "Update session ping")]
+ [Route("/SyncPlay/Ping", "POST", Summary = "Update session ping")]
[Authenticated]
- public class SyncPlayUpdatePing : IReturnVoid
+ public class SyncPlayPing : IReturnVoid
{
[ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")]
public double Ping { get; set; }
@@ -135,7 +128,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayNewGroup request)
+ public void Post(SyncPlayNew request)
{
var currentSession = GetSession(_sessionContext);
_syncPlayManager.NewGroup(currentSession, CancellationToken.None);
@@ -145,30 +138,20 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayJoinGroup request)
+ public void Post(SyncPlayJoin request)
{
var currentSession = GetSession(_sessionContext);
Guid groupId;
- Guid playingItemId = Guid.Empty;
-
if (!Guid.TryParse(request.GroupId, out groupId))
{
Logger.LogError("JoinGroup: {0} is not a valid format for GroupId. Ignoring request.", request.GroupId);
return;
}
- // Both null and empty strings mean that client isn't playing anything
- if (!string.IsNullOrEmpty(request.PlayingItemId) && !Guid.TryParse(request.PlayingItemId, out playingItemId))
- {
- Logger.LogError("JoinGroup: {0} is not a valid format for PlayingItemId. Ignoring request.", request.PlayingItemId);
- return;
- }
-
var joinRequest = new JoinGroupRequest()
{
- GroupId = groupId,
- PlayingItemId = playingItemId
+ GroupId = groupId
};
_syncPlayManager.JoinGroup(currentSession, groupId, joinRequest, CancellationToken.None);
@@ -178,7 +161,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayLeaveGroup request)
+ public void Post(SyncPlayLeave request)
{
var currentSession = GetSession(_sessionContext);
_syncPlayManager.LeaveGroup(currentSession, CancellationToken.None);
@@ -189,7 +172,7 @@ namespace MediaBrowser.Api.SyncPlay
/// </summary>
/// <param name="request">The request.</param>
/// <value>The requested list of groups.</value>
- public List<GroupInfoView> Post(SyncPlayListGroups request)
+ public List<GroupInfoView> Get(SyncPlayList request)
{
var currentSession = GetSession(_sessionContext);
var filterItemId = Guid.Empty;
@@ -206,7 +189,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayPlayRequest request)
+ public void Post(SyncPlayPlay request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -220,7 +203,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayPauseRequest request)
+ public void Post(SyncPlayPause request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -234,7 +217,7 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlaySeekRequest request)
+ public void Post(SyncPlaySeek request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
@@ -249,12 +232,12 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayBufferingRequest request)
+ public void Post(SyncPlayBuffering request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = request.BufferingDone ? PlaybackRequestType.BufferingDone : PlaybackRequestType.Buffering,
+ Type = request.BufferingDone ? PlaybackRequestType.Ready : PlaybackRequestType.Buffer,
When = DateTime.Parse(request.When),
PositionTicks = request.PositionTicks
};
@@ -265,12 +248,12 @@ namespace MediaBrowser.Api.SyncPlay
/// Handles the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(SyncPlayUpdatePing request)
+ public void Post(SyncPlayPing request)
{
var currentSession = GetSession(_sessionContext);
var syncPlayRequest = new PlaybackRequest()
{
- Type = PlaybackRequestType.UpdatePing,
+ Type = PlaybackRequestType.Ping,
Ping = Convert.ToInt64(request.Ping)
};
_syncPlayManager.HandleRequest(currentSession, syncPlayRequest, CancellationToken.None);
diff --git a/MediaBrowser.Api/System/ActivityLogService.cs b/MediaBrowser.Api/System/ActivityLogService.cs
index 7ca31c21a..4afa6e114 100644
--- a/MediaBrowser.Api/System/ActivityLogService.cs
+++ b/MediaBrowser.Api/System/ActivityLogService.cs
@@ -56,7 +56,10 @@ namespace MediaBrowser.Api.System
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
- entries => entries.Where(entry => entry.DateCreated >= minDate));
+ entries => entries.Where(entry => entry.DateCreated >= minDate
+ && (!request.HasUserId.HasValue || (request.HasUserId.Value
+ ? entry.UserId != Guid.Empty
+ : entry.UserId == Guid.Empty))));
var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit);
diff --git a/MediaBrowser.Common/Extensions/HttpContextExtensions.cs b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs
new file mode 100644
index 000000000..d746207c7
--- /dev/null
+++ b/MediaBrowser.Common/Extensions/HttpContextExtensions.cs
@@ -0,0 +1,33 @@
+using MediaBrowser.Model.Services;
+using Microsoft.AspNetCore.Http;
+
+namespace MediaBrowser.Common.Extensions
+{
+ /// <summary>
+ /// Static class containing extension methods for <see cref="HttpContext"/>.
+ /// </summary>
+ public static class HttpContextExtensions
+ {
+ private const string ServiceStackRequest = "ServiceStackRequest";
+
+ /// <summary>
+ /// Set the ServiceStack request.
+ /// </summary>
+ /// <param name="httpContext">The HttpContext instance.</param>
+ /// <param name="request">The service stack request instance.</param>
+ public static void SetServiceStackRequest(this HttpContext httpContext, IRequest request)
+ {
+ httpContext.Items[ServiceStackRequest] = request;
+ }
+
+ /// <summary>
+ /// Get the ServiceStack request.
+ /// </summary>
+ /// <param name="httpContext">The HttpContext instance.</param>
+ /// <returns>The service stack request instance.</returns>
+ public static IRequest GetServiceStackRequest(this HttpContext httpContext)
+ {
+ return (IRequest)httpContext.Items[ServiceStackRequest];
+ }
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index c9ca153c7..7380f39fd 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -17,8 +17,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
</ItemGroup>
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 25933bc90..90f62e153 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -613,7 +613,7 @@ namespace MediaBrowser.Controller.Entities
{
if (!IsFileProtocol)
{
- return new string[] { };
+ return Array.Empty<string>();
}
return new[] { Path };
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 70c48b6f1..c131c5430 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -198,7 +198,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public Guid[] GetLibraryFolderIds()
{
- var expandedFolders = new List<Guid>() { };
+ var expandedFolders = new List<Guid>();
return FlattenItems(this, expandedFolders)
.SelectMany(i => LibraryManager.GetCollectionFolders(i))
diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs
index fc9b3f1c6..a3aa6019e 100644
--- a/MediaBrowser.Controller/Library/TVUtils.cs
+++ b/MediaBrowser.Controller/Library/TVUtils.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Library
};
}
- return new DayOfWeek[] { };
+ return Array.Empty<DayOfWeek>();
}
return null;
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 73e966344..67f17f7a5 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -13,8 +13,8 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.6" />
</ItemGroup>
<ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index d3fb6a46d..04d03080b 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
+using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Jellyfin.Data.Enums;
@@ -449,41 +450,59 @@ namespace MediaBrowser.Controller.MediaEncoding
var arg = new StringBuilder();
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
- bool isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- bool isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- bool isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
- bool isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- if (state.IsVideoRequest
- && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ if (!IsCopyCodec(outputVideoCodec))
{
- if (isVaapiDecoder)
+ if (state.IsVideoRequest
+ && _mediaEncoder.SupportsHwaccel("vaapi")
+ && string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- arg.Append("-hwaccel_output_format vaapi ")
- .Append("-vaapi_device ")
- .Append(encodingOptions.VaapiDevice)
- .Append(" ");
- }
- else if (!isVaapiDecoder && isVaapiEncoder)
- {
- arg.Append("-vaapi_device ")
- .Append(encodingOptions.VaapiDevice)
- .Append(" ");
+ if (isVaapiDecoder)
+ {
+ arg.Append("-hwaccel_output_format vaapi ")
+ .Append("-vaapi_device ")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(" ");
+ }
+ else if (!isVaapiDecoder && isVaapiEncoder)
+ {
+ arg.Append("-vaapi_device ")
+ .Append(encodingOptions.VaapiDevice)
+ .Append(" ");
+ }
}
- }
-
- if (state.IsVideoRequest
- && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- if (!hasTextSubs)
+ if (state.IsVideoRequest
+ && string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
- if (isQsvEncoder)
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+
+ if (isQsvEncoder)
{
if (isQsvDecoder)
{
- arg.Append("-hwaccel qsv ");
+ if (isLinux)
+ {
+ if (hasGraphicalSubs)
+ {
+ arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
+ }
+ else
+ {
+ arg.Append("-hwaccel qsv ");
+ }
+ }
+
+ if (isWindows)
+ {
+ arg.Append("-hwaccel qsv ");
+ }
}
// While using SW decoder
else
@@ -806,6 +825,34 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
}
+ else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ switch (encodingOptions.EncoderPreset)
+ {
+ case "veryslow":
+ case "slow":
+ case "slower":
+ param += "-quality quality";
+ break;
+
+ case "medium":
+ param += "-quality balanced";
+ break;
+
+ case "fast":
+ case "faster":
+ case "veryfast":
+ case "superfast":
+ case "ultrafast":
+ param += "-quality speed";
+ break;
+
+ default:
+ param += "-quality speed";
+ break;
+ }
+ }
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
{
// Values 0-3, 0 being highest quality but slower
@@ -1351,7 +1398,7 @@ namespace MediaBrowser.Controller.MediaEncoding
transcoderChannelLimit = 6;
}
- var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec);
+ var isTranscodingAudio = !IsCopyCodec(codec);
int? resultChannels = state.GetRequestedAudioChannels(codec);
if (isTranscodingAudio)
@@ -1555,28 +1602,44 @@ namespace MediaBrowser.Controller.MediaEncoding
var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
}
else
{
- index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
+ index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
if (index != -1)
{
- outputSizeParam = "," + outputSizeParam.Substring(index);
+ outputSizeParam = outputSizeParam.Substring(index);
+ }
+ else
+ {
+ index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
+ {
+ outputSizeParam = outputSizeParam.Substring(index);
+ }
+ else
+ {
+ index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
+ if (index != -1)
+ {
+ outputSizeParam = outputSizeParam.Substring(index);
+ }
+ }
}
}
}
@@ -1585,43 +1648,30 @@ namespace MediaBrowser.Controller.MediaEncoding
var videoSizeParam = string.Empty;
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
// Setup subtitle scaling
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
{
- videoSizeParam = string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}:{1}",
- state.VideoStream.Width.Value,
- state.VideoStream.Height.Value);
-
- // For QSV, feed it into hardware encoder now
- if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- videoSizeParam += ",hwupload=extra_hw_frames=64";
- }
+ // Adjust the size of graphical subtitles to fit the video stream.
+ var videoStream = state.VideoStream;
+ var inputWidth = videoStream?.Width;
+ var inputHeight = videoStream?.Height;
+ var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
- // For VAAPI and CUVID decoder
- // these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
- // thus needs to be manually adjusted.
- if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
- || (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
- && (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
- || outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)))
+ if (width.HasValue && height.HasValue)
{
- var videoStream = state.VideoStream;
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
- var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
-
- if (width.HasValue && height.HasValue)
- {
- videoSizeParam = string.Format(
+ videoSizeParam = string.Format(
CultureInfo.InvariantCulture,
- "scale={0}:{1}",
+ "scale={0}x{1}",
width.Value,
height.Value);
- }
+ }
+
+ // For QSV, feed it into hardware encoder now
+ if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ videoSizeParam += ",hwupload=extra_hw_frames=64";
}
}
@@ -1634,7 +1684,10 @@ namespace MediaBrowser.Controller.MediaEncoding
: state.SubtitleStream.Index;
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
- var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
+ // Always put the scaler before the overlay for better performance
+ var retStr = !string.IsNullOrEmpty(outputSizeParam) ?
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" :
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
// When the input may or may not be hardware VAAPI decodable
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
@@ -1644,12 +1697,11 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
+ else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
{
/*
@@ -1657,7 +1709,6 @@ namespace MediaBrowser.Controller.MediaEncoding
[sub]: SW scaling subtitle to FixedOutputSize
[base][sub]: SW overlay
*/
- outputSizeParam = outputSizeParam.TrimStart(',');
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
}
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -1666,14 +1717,13 @@ namespace MediaBrowser.Controller.MediaEncoding
QSV in FFMpeg can now setup hardware overlay for transcodes.
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
with fixed frame size.
+ Currently only supports linux.
*/
- if (videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
+ if (isLinux)
{
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
- }
- else
- {
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
+ retStr = !string.IsNullOrEmpty(outputSizeParam) ?
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
+ " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
}
}
@@ -1745,10 +1795,8 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs))
+ || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
&& width.HasValue
&& height.HasValue)
{
@@ -1757,7 +1805,11 @@ namespace MediaBrowser.Controller.MediaEncoding
// output dimensions. Output dimensions are guaranteed to be even.
var outputWidth = width.Value;
var outputHeight = height.Value;
- var vaapi_or_qsv = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ? "qsv" : "vaapi";
+ var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
+ var isDeintEnabled = state.DeInterlace("h264", true)
+ || state.DeInterlace("avc", true)
+ || state.DeInterlace("h265", true)
+ || state.DeInterlace("hevc", true);
if (!videoWidth.HasValue
|| outputWidth != videoWidth.Value
@@ -1765,17 +1817,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|| outputHeight != videoHeight.Value)
{
// Force nv12 pixel format to enable 10-bit to 8-bit colour conversion.
+ // use vpp_qsv filter to avoid green bar when the fixed output size is requested.
filters.Add(
string.Format(
CultureInfo.InvariantCulture,
- "scale_{0}=w={1}:h={2}:format=nv12",
- vaapi_or_qsv,
+ "{0}=w={1}:h={2}:format=nv12{3}",
+ qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
outputWidth,
- outputHeight));
+ outputHeight,
+ (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
}
else
{
- filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
+ filters.Add(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}=format=nv12{1}",
+ qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
+ (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
}
}
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
@@ -1987,7 +2046,6 @@ namespace MediaBrowser.Controller.MediaEncoding
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
var request = state.BaseRequest;
-
var videoStream = state.VideoStream;
var filters = new List<string>();
@@ -1996,32 +2054,34 @@ namespace MediaBrowser.Controller.MediaEncoding
var inputHeight = videoStream?.Height;
var threeDFormat = state.MediaSource.Video3DFormat;
+ var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
+ var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
+ var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
+ var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
// When the input may or may not be hardware VAAPI decodable
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (isVaapiH264Encoder)
{
filters.Add("format=nv12|vaapi");
filters.Add("hwupload");
}
- // When the input may or may not be hardware QSV decodable
- else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context
+ else if (isLinux && hasGraphicalSubs && isQsvH264Encoder)
{
- if (!hasTextSubs)
- {
- filters.Add("format=nv12|qsv");
- filters.Add("hwupload=extra_hw_frames=64");
- }
+ filters.Add("hwupload=extra_hw_frames=64");
}
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
- && string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
{
var codec = videoStream.Codec.ToLowerInvariant();
- var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
+ var isColorDepth10 = IsColorDepth10(state);
// Assert 10-bit hardware VAAPI decodable
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
@@ -2046,49 +2106,49 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// Add hardware deinterlace filter before scaling filter
- if (state.DeInterlace("h264", true))
+ if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true))
{
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (isVaapiH264Encoder)
{
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
}
- else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- {
- if (!hasTextSubs)
- {
- filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
- }
- }
}
// Add software deinterlace filter before scaling filter
- if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
- && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
- || (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)))
+ if (state.DeInterlace("h264", true)
+ || state.DeInterlace("avc", true)
+ || state.DeInterlace("h265", true)
+ || state.DeInterlace("hevc", true))
{
+ var deintParam = string.Empty;
var inputFramerate = videoStream?.RealFrameRate;
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
{
- filters.Add("yadif=1:-1:0");
+ deintParam = "yadif=1:-1:0";
}
else
{
- filters.Add("yadif=0:-1:0");
+ deintParam = "yadif=0:-1:0";
+ }
+
+ if (!string.IsNullOrEmpty(deintParam))
+ {
+ if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
+ {
+ filters.Add(deintParam);
+ }
}
}
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
- // Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
+ if (isVaapiH264Encoder)
{
- if (state.SubtitleStream != null
- && state.SubtitleStream.IsTextSubtitleStream
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+ if (hasTextSubs)
{
// Test passed on Intel and AMD gfx
filters.Add("hwmap=mode=read+write");
@@ -2098,9 +2158,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var output = string.Empty;
- if (state.SubtitleStream != null
- && state.SubtitleStream.IsTextSubtitleStream
- && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
+ if (hasTextSubs)
{
var subParam = GetTextSubtitleParam(state);
@@ -2108,7 +2166,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
// Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (isVaapiH264Encoder)
{
filters.Add("hwmap");
}
@@ -2262,7 +2320,7 @@ namespace MediaBrowser.Controller.MediaEncoding
flags.Add("+ignidx");
}
- if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
{
flags.Add("+genpts");
}
@@ -2288,7 +2346,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
inputModifier += " " + videoDecoder;
- if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
+ if (!IsCopyCodec(state.OutputVideoCodec)
+ && (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
{
var videoStream = state.VideoStream;
var inputWidth = videoStream?.Width;
@@ -2301,11 +2360,19 @@ namespace MediaBrowser.Controller.MediaEncoding
&& width.HasValue
&& height.HasValue)
{
- inputModifier += string.Format(
- CultureInfo.InvariantCulture,
- " -resize {0}x{1}",
- width.Value,
- height.Value);
+ if (width.HasValue && height.HasValue)
+ {
+ inputModifier += string.Format(
+ CultureInfo.InvariantCulture,
+ " -resize {0}x{1}",
+ width.Value,
+ height.Value);
+ }
+
+ if (state.DeInterlace("h264", true))
+ {
+ inputModifier += " -deint 1";
+ }
}
}
}
@@ -2521,21 +2588,21 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
- /// Gets the name of the output video codec.
+ /// Gets the ffmpeg option string for the hardware accelerated video decoder.
/// </summary>
+ /// <param name="state">The encoding job info.</param>
+ /// <param name="encodingOptions">The encoding options.</param>
+ /// <returns>The option string or null if none available.</returns>
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
{
- var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
var videoStream = state.VideoStream;
- var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
-
- if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
+ if (videoStream == null)
{
return null;
}
+ var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
// Only use alternative encoders for video files.
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
@@ -2544,10 +2611,15 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
- if (videoStream != null
- && !string.IsNullOrEmpty(videoStream.Codec)
- && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ if (IsCopyCodec(state.OutputVideoCodec))
{
+ return null;
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
+ {
+ var isColorDepth10 = IsColorDepth10(state);
+
// Only hevc and vp9 formats have 10-bit hardware decoder support now.
if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
@@ -2623,13 +2695,6 @@ namespace MediaBrowser.Controller.MediaEncoding
case "h264":
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
{
- // cuvid decoder does not support 10-bit input.
- if ((videoStream.BitDepth ?? 8) > 8)
- {
- encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
- return null;
- }
-
return "-c:v h264_cuvid";
}
@@ -2896,21 +2961,24 @@ namespace MediaBrowser.Controller.MediaEncoding
/// </summary>
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
{
- var isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
+ var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
{
- if (!isWindows)
+ if (isLinux)
{
return "-hwaccel vaapi";
}
- else if (isWindows8orLater)
+
+ if (isWindows && isWindows8orLater)
{
return "-hwaccel d3d11va";
}
- else
+
+ if (isWindows && !isWindows8orLater)
{
return "-hwaccel dxva2";
}
@@ -3000,7 +3068,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -mpegts_m2ts_mode 1";
}
- if (EncodingHelper.IsCopyCodec(videoCodec))
+ if (IsCopyCodec(videoCodec))
{
if (state.VideoStream != null
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
@@ -3102,7 +3170,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var args = "-codec:a:0 " + codec;
- if (EncodingHelper.IsCopyCodec(codec))
+ if (IsCopyCodec(codec))
{
return args;
}
@@ -3179,5 +3247,42 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
}
+
+ public static bool IsColorDepth10(EncodingJobInfo state)
+ {
+ var result = false;
+ var videoStream = state.VideoStream;
+
+ if (videoStream != null)
+ {
+ if (!string.IsNullOrEmpty(videoStream.PixelFormat))
+ {
+ result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ if (!string.IsNullOrEmpty(videoStream.Profile))
+ {
+ result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
+ || videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase);
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ result = (videoStream.BitDepth ?? 8) == 10;
+ if (result)
+ {
+ return true;
+ }
+ }
+
+ return result;
+ }
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
index d0fac1efa..e742df517 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupInfo.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <summary>
/// Gets the default ping value used for sessions.
/// </summary>
- public long DefaulPing { get; } = 500;
+ public long DefaultPing { get; } = 500;
/// <summary>
/// Gets or sets the group identifier.
@@ -70,16 +70,16 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void AddSession(SessionInfo session)
{
- if (ContainsSession(session.Id.ToString()))
+ if (ContainsSession(session.Id))
{
return;
}
var member = new GroupMember();
member.Session = session;
- member.Ping = DefaulPing;
+ member.Ping = DefaultPing;
member.IsBuffering = false;
- Participants[session.Id.ToString()] = member;
+ Participants[session.Id] = member;
}
/// <summary>
@@ -88,12 +88,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="session">The session.</param>
public void RemoveSession(SessionInfo session)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants.Remove(session.Id.ToString(), out _);
+ Participants.Remove(session.Id, out _);
}
/// <summary>
@@ -103,12 +103,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="ping">The ping.</param>
public void UpdatePing(SessionInfo session, long ping)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].Ping = ping;
+ Participants[session.Id].Ping = ping;
}
/// <summary>
@@ -117,7 +117,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// <value name="session">The highest ping in the group.</value>
public long GetHighestPing()
{
- long max = Int64.MinValue;
+ long max = long.MinValue;
foreach (var session in Participants.Values)
{
max = Math.Max(max, session.Ping);
@@ -133,12 +133,12 @@ namespace MediaBrowser.Controller.SyncPlay
/// <param name="isBuffering">The state.</param>
public void SetBuffering(SessionInfo session, bool isBuffering)
{
- if (!ContainsSession(session.Id.ToString()))
+ if (!ContainsSession(session.Id))
{
return;
}
- Participants[session.Id.ToString()].IsBuffering = isBuffering;
+ Participants[session.Id].IsBuffering = isBuffering;
}
/// <summary>
diff --git a/MediaBrowser.Controller/SyncPlay/GroupMember.cs b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
index a3975c334..cde6f8e8c 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupMember.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupMember.cs
@@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.SyncPlay
public class GroupMember
{
/// <summary>
- /// Gets or sets whether this member is buffering.
+ /// Gets or sets a value indicating whether this member is buffering.
/// </summary>
/// <value><c>true</c> if member is buffering; <c>false</c> otherwise.</value>
public bool IsBuffering { get; set; }
diff --git a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
index de1fcd259..45c543806 100644
--- a/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
+++ b/MediaBrowser.Controller/SyncPlay/ISyncPlayController.cs
@@ -33,7 +33,7 @@ namespace MediaBrowser.Controller.SyncPlay
/// </summary>
/// <param name="session">The session.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- void InitGroup(SessionInfo session, CancellationToken cancellationToken);
+ void CreateGroup(SessionInfo session, CancellationToken cancellationToken);
/// <summary>
/// Adds the session to the group.
diff --git a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
index e6359f4fb..ccfae2fa5 100644
--- a/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
+++ b/MediaBrowser.MediaEncoding/BdInfo/BdInfoExaminer.cs
@@ -41,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
var outputStream = new BlurayDiscInfo
{
- MediaStreams = new MediaStream[] { }
+ MediaStreams = Array.Empty<MediaStream>()
};
if (playlist == null)
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 4250edfb7..0fd0239b4 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -90,6 +90,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// $ ffmpeg -version | perl -ne ' print "$1=$2.$3," if /^(lib\w+)\s+(\d+)\.\s*(\d+)/'
private static readonly IReadOnlyDictionary<string, Version> _ffmpegVersionMap = new Dictionary<string, Version>
{
+ { "libavutil=56.51,libavcodec=58.91,libavformat=58.45,libavdevice=58.10,libavfilter=7.85,libswscale=5.7,libswresample=3.7,libpostproc=55.7,", new Version(4, 3) },
{ "libavutil=56.31,libavcodec=58.54,libavformat=58.29,libavdevice=58.8,libavfilter=7.57,libswscale=5.5,libswresample=3.5,libpostproc=55.5,", new Version(4, 2) },
{ "libavutil=56.22,libavcodec=58.35,libavformat=58.20,libavdevice=58.5,libavfilter=7.40,libswscale=5.3,libswresample=3.3,libpostproc=55.3,", new Version(4, 1) },
{ "libavutil=56.14,libavcodec=58.18,libavformat=58.12,libavdevice=58.3,libavfilter=7.16,libswscale=5.1,libswresample=3.1,libpostproc=55.1,", new Version(4, 0) },
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 3f21c2bd4..c85d963ed 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -1008,7 +1008,7 @@ namespace MediaBrowser.MediaEncoding.Probing
var artist = FFProbeHelpers.GetDictionaryValue(tags, "artist");
if (string.IsNullOrWhiteSpace(artist))
{
- audio.Artists = new string[] { };
+ audio.Artists = Array.Empty<string>();
}
else
{
@@ -1031,7 +1031,7 @@ namespace MediaBrowser.MediaEncoding.Probing
if (string.IsNullOrWhiteSpace(albumArtist))
{
- audio.AlbumArtists = new string[] { };
+ audio.AlbumArtists = Array.Empty<string>();
}
else
{
diff --git a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
index 398c5db8c..bdc5f8bb7 100644
--- a/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
+++ b/MediaBrowser.Model/Dlna/MediaFormatProfileResolver.cs
@@ -206,7 +206,7 @@ namespace MediaBrowser.Model.Dlna
}
}
- return new MediaFormatProfile[] { };
+ return Array.Empty<MediaFormatProfile>();
}
private MediaFormatProfile ValueOf(string value)
diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs
index b89e9ce90..204340c46 100644
--- a/MediaBrowser.Model/Dlna/StreamInfo.cs
+++ b/MediaBrowser.Model/Dlna/StreamInfo.cs
@@ -144,7 +144,7 @@ namespace MediaBrowser.Model.Dlna
public Dictionary<string, string> StreamOptions { get; private set; }
- public string MediaSourceId => MediaSource == null ? null : MediaSource.Id;
+ public string MediaSourceId => MediaSource?.Id;
public bool IsDirectStream =>
PlayMethod == PlayMethod.DirectStream ||
@@ -813,18 +813,18 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetAudioStream;
- string inputCodec = stream == null ? null : stream.Codec;
+ string inputCodec = stream?.Codec;
if (IsDirectStream)
{
- return string.IsNullOrEmpty(inputCodec) ? new string[] { } : new[] { inputCodec };
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
}
foreach (string codec in AudioCodecs)
{
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
+ return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
}
}
@@ -838,18 +838,18 @@ namespace MediaBrowser.Model.Dlna
{
var stream = TargetVideoStream;
- string inputCodec = stream == null ? null : stream.Codec;
+ string inputCodec = stream?.Codec;
if (IsDirectStream)
{
- return string.IsNullOrEmpty(inputCodec) ? new string[] { } : new[] { inputCodec };
+ return string.IsNullOrEmpty(inputCodec) ? Array.Empty<string>() : new[] { inputCodec };
}
foreach (string codec in VideoCodecs)
{
if (string.Equals(codec, inputCodec, StringComparison.OrdinalIgnoreCase))
{
- return string.IsNullOrEmpty(codec) ? new string[] { } : new[] { codec };
+ return string.IsNullOrEmpty(codec) ? Array.Empty<string>() : new[] { codec };
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 83bd0c07e..902e29b20 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -23,7 +23,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
- <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
<PackageReference Include="System.Globalization" Version="4.3.0" />
<PackageReference Include="System.Text.Json" Version="4.7.2" />
</ItemGroup>
diff --git a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
index 12d537492..2ef6f7c60 100644
--- a/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
+++ b/MediaBrowser.Model/Querying/UpcomingEpisodesQuery.cs
@@ -1,6 +1,7 @@
#nullable disable
#pragma warning disable CS1591
+using System;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Model.Querying
@@ -54,7 +55,7 @@ namespace MediaBrowser.Model.Querying
public UpcomingEpisodesQuery()
{
- EnableImageTypes = new ImageType[] { };
+ EnableImageTypes = Array.Empty<ImageType>();
}
}
}
diff --git a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
index d67b6bd55..0c77a6132 100644
--- a/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
+++ b/MediaBrowser.Model/SyncPlay/JoinGroupRequest.cs
@@ -12,11 +12,5 @@ namespace MediaBrowser.Model.SyncPlay
/// </summary>
/// <value>The Group id to join.</value>
public Guid GroupId { get; set; }
-
- /// <summary>
- /// Gets or sets the playing item id.
- /// </summary>
- /// <value>The client's currently playing item id.</value>
- public Guid PlayingItemId { get; set; }
}
}
diff --git a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
index 671f4e01f..e89efeed8 100644
--- a/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
+++ b/MediaBrowser.Model/SyncPlay/PlaybackRequestType.cs
@@ -23,16 +23,16 @@ namespace MediaBrowser.Model.SyncPlay
/// <summary>
/// A user is signaling that playback is buffering.
/// </summary>
- Buffering = 3,
+ Buffer = 3,
/// <summary>
/// A user is signaling that playback resumed.
/// </summary>
- BufferingDone = 4,
+ Ready = 4,
/// <summary>
/// A user is reporting its ping.
/// </summary>
- UpdatePing = 5
+ Ping = 5
}
}
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index ecfa3d46d..3b0c7b56c 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -723,7 +723,7 @@ namespace MediaBrowser.Providers.Manager
userDataList.AddRange(localItem.UserDataList);
}
- MergeData(localItem, temp, new MetadataField[] { }, !options.ReplaceAllMetadata, true);
+ MergeData(localItem, temp, Array.Empty<MetadataField>(), !options.ReplaceAllMetadata, true);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport;
// Only one local provider allowed per item
@@ -849,7 +849,7 @@ namespace MediaBrowser.Providers.Manager
{
result.Provider = provider.Name;
- MergeData(result, temp, new MetadataField[] { }, false, false);
+ MergeData(result, temp, Array.Empty<MetadataField>(), false, false);
MergeNewData(temp.Item, id);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 86a182fe5..4ee065cd1 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -25,7 +23,6 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
-using MediaBrowser.Model.Serialization;
using Microsoft.Extensions.Logging;
using Priority_Queue;
using Book = MediaBrowser.Controller.Entities.Book;
@@ -42,33 +39,38 @@ namespace MediaBrowser.Providers.Manager
/// </summary>
public class ProviderManager : IProviderManager, IDisposable
{
+ private readonly object _refreshQueueLock = new object();
private readonly ILogger<ProviderManager> _logger;
private readonly IHttpClient _httpClient;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
- private readonly IJsonSerializer _json;
private readonly ILibraryManager _libraryManager;
private readonly ISubtitleManager _subtitleManager;
private readonly IServerConfigurationManager _configurationManager;
+ private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
+ private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
+ private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
+ new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
- private IImageProvider[] ImageProviders { get; set; }
-
- private IMetadataService[] _metadataServices = { };
- private IMetadataProvider[] _metadataProviders = { };
+ private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
+ private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
private IEnumerable<IMetadataSaver> _savers;
-
private IExternalId[] _externalIds;
-
- private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
-
- public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
- public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
- public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
+ private bool _isProcessingRefreshQueue;
+ private bool _disposed;
/// <summary>
- /// Initializes a new instance of the <see cref="ProviderManager" /> class.
+ /// Initializes a new instance of the <see cref="ProviderManager"/> class.
/// </summary>
+ /// <param name="httpClient">The Http client.</param>
+ /// <param name="subtitleManager">The subtitle manager.</param>
+ /// <param name="configurationManager">The configuration manager.</param>
+ /// <param name="libraryMonitor">The library monitor.</param>
+ /// <param name="logger">The logger.</param>
+ /// <param name="fileSystem">The filesystem.</param>
+ /// <param name="appPaths">The server application paths.</param>
+ /// <param name="libraryManager">The library manager.</param>
public ProviderManager(
IHttpClient httpClient,
ISubtitleManager subtitleManager,
@@ -77,8 +79,7 @@ namespace MediaBrowser.Providers.Manager
ILogger<ProviderManager> logger,
IFileSystem fileSystem,
IServerApplicationPaths appPaths,
- ILibraryManager libraryManager,
- IJsonSerializer json)
+ ILibraryManager libraryManager)
{
_logger = logger;
_httpClient = httpClient;
@@ -87,16 +88,27 @@ namespace MediaBrowser.Providers.Manager
_fileSystem = fileSystem;
_appPaths = appPaths;
_libraryManager = libraryManager;
- _json = json;
_subtitleManager = subtitleManager;
}
- /// <summary>
- /// Adds the metadata providers.
- /// </summary>
- public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
- IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
- IEnumerable<IExternalId> externalIds)
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
+
+ /// <inheritdoc/>
+ public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
+
+ private IImageProvider[] ImageProviders { get; set; }
+
+ /// <inheritdoc/>
+ public void AddParts(
+ IEnumerable<IImageProvider> imageProviders,
+ IEnumerable<IMetadataService> metadataServices,
+ IEnumerable<IMetadataProvider> metadataProviders,
+ IEnumerable<IMetadataSaver> metadataSavers,
+ IEnumerable<IExternalId> externalIds)
{
ImageProviders = imageProviders.ToArray();
@@ -104,27 +116,17 @@ namespace MediaBrowser.Providers.Manager
_metadataProviders = metadataProviders.ToArray();
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
- _savers = metadataSavers.Where(i =>
- {
- var configurable = i as IConfigurableProvider;
-
- return configurable == null || configurable.IsEnabled;
- }).ToArray();
+ _savers = metadataSavers
+ .Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled)
+ .ToArray();
}
+ /// <inheritdoc/>
public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
- IMetadataService service = null;
var type = item.GetType();
- foreach (var current in _metadataServices)
- {
- if (current.CanRefreshPrimary(type))
- {
- service = current;
- break;
- }
- }
+ var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type));
if (service == null)
{
@@ -147,35 +149,36 @@ namespace MediaBrowser.Providers.Manager
return Task.FromResult(ItemUpdateType.None);
}
+ /// <inheritdoc/>
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
- using (var response = await _httpClient.GetResponse(new HttpRequestOptions
+ using var response = await _httpClient.GetResponse(new HttpRequestOptions
{
CancellationToken = cancellationToken,
Url = url,
BufferContent = false
+ }).ConfigureAwait(false);
- }).ConfigureAwait(false))
+ // Workaround for tvheadend channel icons
+ // TODO: Isolate this hack into the tvh plugin
+ if (string.IsNullOrEmpty(response.ContentType))
{
- // Workaround for tvheadend channel icons
- // TODO: Isolate this hack into the tvh plugin
- if (string.IsNullOrEmpty(response.ContentType))
+ if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
{
- if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
- {
- response.ContentType = "image/png";
- }
+ response.ContentType = "image/png";
}
-
- await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
}
+
+ await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
}
+ /// <inheritdoc/>
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
{
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
}
+ /// <inheritdoc/>
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(source))
@@ -188,12 +191,14 @@ namespace MediaBrowser.Providers.Manager
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
}
+ /// <inheritdoc/>
public Task SaveImage(User user, Stream source, string mimeType, string path)
{
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
.SaveImage(user, source, path);
}
+ /// <inheritdoc/>
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
{
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
@@ -213,7 +218,7 @@ namespace MediaBrowser.Providers.Manager
languages.Add(preferredLanguage);
}
- var tasks = providers.Select(i => GetImages(item, cancellationToken, i, languages, query.ImageType));
+ var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
@@ -224,12 +229,17 @@ namespace MediaBrowser.Providers.Manager
/// Gets the images.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="provider">The provider.</param>
/// <param name="preferredLanguages">The preferred languages.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
/// <param name="type">The type.</param>
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
- private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null)
+ private async Task<IEnumerable<RemoteImageInfo>> GetImages(
+ BaseItem item,
+ IRemoteImageProvider provider,
+ IReadOnlyCollection<string> preferredLanguages,
+ CancellationToken cancellationToken,
+ ImageType? type = null)
{
try
{
@@ -260,16 +270,18 @@ namespace MediaBrowser.Providers.Manager
}
}
- /// <summary>
- /// Gets the supported image providers.
- /// </summary>
- /// <param name="item">The item.</param>
- /// <returns>IEnumerable{IImageProvider}.</returns>
+ /// <inheritdoc/>
public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
{
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray()));
}
+ /// <summary>
+ /// Gets the image providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="refreshOptions">The image refresh options.</param>
+ /// <returns>The image providers for the item.</returns>
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
{
return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
@@ -283,7 +295,7 @@ namespace MediaBrowser.Providers.Manager
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
var typeFetcherOrder = typeOptions?.ImageFetcherOrder;
- return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled))
+ return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled))
.OrderBy(i =>
{
// See if there's a user-defined order
@@ -304,6 +316,13 @@ namespace MediaBrowser.Providers.Manager
.ThenBy(GetOrder);
}
+ /// <summary>
+ /// Gets the metadata providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="libraryOptions">The library options.</param>
+ /// <typeparam name="T">The type of metadata provider.</typeparam>
+ /// <returns>The metadata providers.</returns>
public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
where T : BaseItem
{
@@ -319,7 +338,7 @@ namespace MediaBrowser.Providers.Manager
var currentOptions = globalMetadataOptions;
return _metadataProviders.OfType<IMetadataProvider<T>>()
- .Where(i => CanRefresh(i, item, libraryOptions, currentOptions, includeDisabled, forceEnableInternetMetadata))
+ .Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata))
.OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions))
.ThenBy(GetDefaultOrder);
}
@@ -329,14 +348,20 @@ namespace MediaBrowser.Providers.Manager
var options = GetMetadataOptions(item);
var libraryOptions = _libraryManager.GetLibraryOptions(item);
- return GetImageProviders(item, libraryOptions, options,
- new ImageRefreshOptions(
- new DirectoryService(_fileSystem)),
- includeDisabled)
- .OfType<IRemoteImageProvider>();
+ return GetImageProviders(
+ item,
+ libraryOptions,
+ options,
+ new ImageRefreshOptions(new DirectoryService(_fileSystem)),
+ includeDisabled).OfType<IRemoteImageProvider>();
}
- private bool CanRefresh(IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata)
+ private bool CanRefresh(
+ IMetadataProvider provider,
+ BaseItem item,
+ LibraryOptions libraryOptions,
+ bool includeDisabled,
+ bool forceEnableInternetMetadata)
{
if (!includeDisabled)
{
@@ -372,7 +397,12 @@ namespace MediaBrowser.Providers.Manager
return true;
}
- private bool CanRefresh(IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
+ private bool CanRefresh(
+ IImageProvider provider,
+ BaseItem item,
+ LibraryOptions libraryOptions,
+ ImageRefreshOptions refreshOptions,
+ bool includeDisabled)
{
if (!includeDisabled)
{
@@ -412,9 +442,7 @@ namespace MediaBrowser.Providers.Manager
/// <returns>System.Int32.</returns>
private int GetOrder(IImageProvider provider)
{
- var hasOrder = provider as IHasOrder;
-
- if (hasOrder == null)
+ if (!(provider is IHasOrder hasOrder))
{
return 0;
}
@@ -441,7 +469,7 @@ namespace MediaBrowser.Providers.Manager
if (provider is IRemoteMetadataProvider)
{
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
- var typeFetcherOrder = typeOptions == null ? null : typeOptions.MetadataFetcherOrder;
+ var typeFetcherOrder = typeOptions?.MetadataFetcherOrder;
var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
@@ -459,9 +487,7 @@ namespace MediaBrowser.Providers.Manager
private int GetDefaultOrder(IMetadataProvider provider)
{
- var hasOrder = provider as IHasOrder;
-
- if (hasOrder != null)
+ if (provider is IHasOrder hasOrder)
{
return hasOrder.Order;
}
@@ -469,9 +495,10 @@ namespace MediaBrowser.Providers.Manager
return 0;
}
+ /// <inheritdoc/>
public MetadataPluginSummary[] GetAllMetadataPlugins()
{
- return new MetadataPluginSummary[]
+ return new[]
{
GetPluginSummary<Movie>(),
GetPluginSummary<BoxSet>(),
@@ -493,7 +520,7 @@ namespace MediaBrowser.Providers.Manager
where T : BaseItem, new()
{
// Give it a dummy path just so that it looks like a file system item
- var dummy = new T()
+ var dummy = new T
{
Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
ParentId = Guid.NewGuid()
@@ -508,11 +535,12 @@ namespace MediaBrowser.Providers.Manager
var libraryOptions = new LibraryOptions();
- var imageProviders = GetImageProviders(dummy, libraryOptions, options,
- new ImageRefreshOptions(
- new DirectoryService(_fileSystem)),
- true)
- .ToList();
+ var imageProviders = GetImageProviders(
+ dummy,
+ libraryOptions,
+ options,
+ new ImageRefreshOptions(new DirectoryService(_fileSystem)),
+ true).ToList();
var pluginList = summary.Plugins.ToList();
@@ -572,7 +600,6 @@ namespace MediaBrowser.Providers.Manager
private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
where T : BaseItem
{
-
// Locals
list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
{
@@ -588,6 +615,7 @@ namespace MediaBrowser.Providers.Manager
}));
}
+ /// <inheritdoc/>
public MetadataOptions GetMetadataOptions(BaseItem item)
{
var type = item.GetType().Name;
@@ -597,17 +625,13 @@ namespace MediaBrowser.Providers.Manager
new MetadataOptions();
}
- /// <summary>
- /// Saves the metadata.
- /// </summary>
+ /// <inheritdoc/>
public void SaveMetadata(BaseItem item, ItemUpdateType updateType)
{
SaveMetadata(item, updateType, _savers);
}
- /// <summary>
- /// Saves the metadata.
- /// </summary>
+ /// <inheritdoc/>
public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
{
SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)));
@@ -619,7 +643,6 @@ namespace MediaBrowser.Providers.Manager
/// <param name="item">The item.</param>
/// <param name="updateType">Type of the update.</param>
/// <param name="savers">The savers.</param>
- /// <returns>Task.</returns>
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
{
var libraryOptions = _libraryManager.GetLibraryOptions(item);
@@ -628,11 +651,9 @@ namespace MediaBrowser.Providers.Manager
{
_logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
- var fileSaver = saver as IMetadataFileSaver;
-
- if (fileSaver != null)
+ if (saver is IMetadataFileSaver fileSaver)
{
- string path = null;
+ string path;
try
{
@@ -699,11 +720,9 @@ namespace MediaBrowser.Providers.Manager
{
if (updateType >= ItemUpdateType.MetadataEdit)
{
- var fileSaver = saver as IMetadataFileSaver;
-
// Manual edit occurred
// Even if save local is off, save locally anyway if the metadata file already exists
- if (fileSaver == null || !File.Exists(fileSaver.GetSavePath(item)))
+ if (!(saver is IMetadataFileSaver fileSaver) || !File.Exists(fileSaver.GetSavePath(item)))
{
return false;
}
@@ -734,6 +753,7 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken)
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo
@@ -748,7 +768,7 @@ namespace MediaBrowser.Providers.Manager
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
}
- public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
+ private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
where TItemType : BaseItem, new()
where TLookupType : ItemLookupInfo
{
@@ -837,7 +857,9 @@ namespace MediaBrowser.Providers.Manager
return resultList;
}
- private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(IRemoteSearchProvider<TLookupType> provider, TLookupType searchInfo,
+ private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(
+ IRemoteSearchProvider<TLookupType> provider,
+ TLookupType searchInfo,
CancellationToken cancellationToken)
where TLookupType : ItemLookupInfo
{
@@ -853,6 +875,7 @@ namespace MediaBrowser.Providers.Manager
return list;
}
+ /// <inheritdoc/>
public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
{
var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
@@ -865,6 +888,7 @@ namespace MediaBrowser.Providers.Manager
return provider.GetImageResponse(url, cancellationToken);
}
+ /// <inheritdoc/>
public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
{
return _externalIds.Where(i =>
@@ -881,6 +905,7 @@ namespace MediaBrowser.Providers.Manager
});
}
+ /// <inheritdoc/>
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
{
return GetExternalIds(item)
@@ -909,6 +934,7 @@ namespace MediaBrowser.Providers.Manager
}).Where(i => i != null).Concat(item.GetRelatedUrls());
}
+ /// <inheritdoc/>
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
{
return GetExternalIds(item)
@@ -921,8 +947,7 @@ namespace MediaBrowser.Providers.Manager
});
}
- private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
-
+ /// <inheritdoc/>
public Dictionary<Guid, Guid> GetRefreshQueue()
{
lock (_refreshQueueLock)
@@ -938,6 +963,7 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public void OnRefreshStart(BaseItem item)
{
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -945,6 +971,7 @@ namespace MediaBrowser.Providers.Manager
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
+ /// <inheritdoc/>
public void OnRefreshComplete(BaseItem item)
{
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -954,6 +981,7 @@ namespace MediaBrowser.Providers.Manager
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
}
+ /// <inheritdoc/>
public double? GetRefreshProgress(Guid id)
{
if (_activeRefreshes.TryGetValue(id, out double value))
@@ -964,6 +992,7 @@ namespace MediaBrowser.Providers.Manager
return null;
}
+ /// <inheritdoc/>
public void OnRefreshProgress(BaseItem item, double progress)
{
var id = item.Id;
@@ -983,12 +1012,7 @@ namespace MediaBrowser.Providers.Manager
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
}
- private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
- new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
-
- private readonly object _refreshQueueLock = new object();
- private bool _isProcessingRefreshQueue;
-
+ /// <inheritdoc/>
public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority)
{
if (_disposed)
@@ -1032,7 +1056,7 @@ namespace MediaBrowser.Providers.Manager
if (item != null)
{
// Try to throttle this a little bit.
- await Task.Delay(100).ConfigureAwait(false);
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
var task = item is MusicArtist artist
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
@@ -1062,17 +1086,14 @@ namespace MediaBrowser.Providers.Manager
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
// Collection folders don't validate their children so we'll have to simulate that here
-
- if (item is CollectionFolder collectionFolder)
+ switch (item)
{
- await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
- }
- else
- {
- if (item is Folder folder)
- {
+ case CollectionFolder collectionFolder:
+ await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
+ break;
+ case Folder folder:
await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
- }
+ break;
}
}
@@ -1082,7 +1103,7 @@ namespace MediaBrowser.Providers.Manager
{
await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
- await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options, true).ConfigureAwait(false);
+ await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
}
}
@@ -1118,12 +1139,13 @@ namespace MediaBrowser.Providers.Manager
}
}
+ /// <inheritdoc/>
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
return RefreshItem(item, options, cancellationToken);
}
- private bool _disposed;
+ /// <inheritdoc/>
public void Dispose()
{
_disposed = true;
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index 446e27df6..42c7cec53 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -16,8 +16,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
- <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
+ <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.6" />
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
<PackageReference Include="PlaylistsNET" Version="1.0.6" />
<PackageReference Include="TvDbSharper" Version="3.2.0" />
diff --git a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html
index fbf413f2b..82f26a8f2 100644
--- a/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/AudioDb/Configuration/config.html
@@ -28,29 +28,31 @@
pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
};
- $('.configPage').on('pageshow', function () {
- Dashboard.showLoadingMsg();
- ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
- $('#enable').checked = config.Enable;
- $('#replaceAlbumName').checked = config.ReplaceAlbumName;
-
- Dashboard.hideLoadingMsg();
+ document.querySelector('.configPage')
+ .addEventListener('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ document.querySelector('#enable').checked = config.Enable;
+ document.querySelector('#replaceAlbumName').checked = config.ReplaceAlbumName;
+
+ Dashboard.hideLoadingMsg();
+ });
});
- });
-
- $('.configForm').on('submit', function (e) {
- Dashboard.showLoadingMsg();
- var form = this;
- ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
- config.Enable = $('#enable', form).checked;
- config.ReplaceAlbumName = $('#replaceAlbumName', form).checked;
-
- ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ document.querySelector('.configForm')
+ .addEventListener('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ config.Enable = document.querySelector('#enable').checked;
+ config.ReplaceAlbumName = document.querySelector('#replaceAlbumName').checked;
+
+ ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ e.preventDefault();
+ return false;
});
-
- return false;
- });
</script>
</div>
</body>
diff --git a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html
index 90196b046..1945e6cb4 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/Configuration/config.html
@@ -36,33 +36,47 @@
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
};
- $('.musicBrainzConfigPage').on('pageshow', function () {
- Dashboard.showLoadingMsg();
- ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
- $('#server').val(config.Server).change();
- $('#rateLimit').val(config.RateLimit).change();
- $('#enable').checked = config.Enable;
- $('#replaceArtistName').checked = config.ReplaceArtistName;
+ document.querySelector('.musicBrainzConfigPage')
+ .addEventListener('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
+ var server = document.querySelector('#server');
+ server.value = config.Server;
+ server.dispatchEvent(new Event('change', {
+ bubbles: true,
+ cancelable: false
+ }));
+
+ var rateLimit = document.querySelector('#rateLimit');
+ rateLimit.value = config.RateLimit;
+ rateLimit.dispatchEvent(new Event('change', {
+ bubbles: true,
+ cancelable: false
+ }));
+
+ document.querySelector('#enable').checked = config.Enable;
+ document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
- Dashboard.hideLoadingMsg();
+ Dashboard.hideLoadingMsg();
+ });
});
- });
-
- $('.musicBrainzConfigForm').on('submit', function (e) {
- Dashboard.showLoadingMsg();
-
- var form = this;
- ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
- config.Server = $('#server', form).val();
- config.RateLimit = $('#rateLimit', form).val();
- config.Enable = $('#enable', form).checked;
- config.ReplaceArtistName = $('#replaceArtistName', form).checked;
-
- ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+
+ document.querySelector('.musicBrainzConfigForm')
+ .addEventListener('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
+ config.Server = document.querySelector('#server').value;
+ config.RateLimit = document.querySelector('#rateLimit').value;
+ config.Enable = document.querySelector('#enable').checked;
+ config.ReplaceArtistName = document.querySelector('#replaceArtistName').checked;
+
+ ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ e.preventDefault();
+ return false;
});
-
- return false;
- });
</script>
</div>
</body>
diff --git a/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html
index 8b117ec8d..f4375b3cb 100644
--- a/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html
+++ b/MediaBrowser.Providers/Plugins/Omdb/Configuration/config.html
@@ -24,25 +24,28 @@
pluginId: "a628c0da-fac5-4c7e-9d1a-7134223f14c8"
};
- $('.configPage').on('pageshow', function () {
- Dashboard.showLoadingMsg();
- ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
- $('#castAndCrew').checked = config.CastAndCrew;
- Dashboard.hideLoadingMsg();
+ document.querySelector('.configPage')
+ .addEventListener('pageshow', function () {
+ Dashboard.showLoadingMsg();
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ document.querySelector('#castAndCrew').checked = config.CastAndCrew;
+ Dashboard.hideLoadingMsg();
+ });
});
- });
- $('.configForm').on('submit', function (e) {
- Dashboard.showLoadingMsg();
-
- var form = this;
- ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
- config.CastAndCrew = $('#castAndCrew', form).checked;
- ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+
+ document.querySelector('.configForm')
+ .addEventListener('submit', function (e) {
+ Dashboard.showLoadingMsg();
+
+ ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
+ config.CastAndCrew = document.querySelector('#castAndCrew').checked;
+ ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
+ });
+
+ e.preventDefault();
+ return false;
});
-
- return false;
- });
</script>
</div>
</body>
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
index 2c6682f82..cd2f96f14 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbClientManager.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Providers;
@@ -229,6 +230,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
}
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ if (imagesSummary.Data.Series > 0)
+ {
+ yield return KeyType.Series;
+ }
+
+ if (imagesSummary.Data.Poster > 0)
+ {
+ yield return KeyType.Poster;
+ }
+ }
+
+ public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
+ var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
+
+ if (imagesSummary.Data.Season > 0)
+ {
+ yield return KeyType.Season;
+ }
+
+ if (imagesSummary.Data.Fanart > 0)
+ {
+ yield return KeyType.Fanart;
+ }
+
+ // TODO seasonwide is not supported in TvDbSharper
+ }
+
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
{
if (_cache.TryGetValue(key, out T cachedValue))
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
index 5af99a573..e9ba20475 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeasonImageProvider.cs
@@ -65,8 +65,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
- foreach (var keyType in keyTypes)
+ var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
+ await foreach (var keyType in keyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
index 7dd012825..c33aefbc1 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesImageProvider.cs
@@ -59,9 +59,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
var language = item.GetPreferredMetadataLanguage();
var remoteImages = new List<RemoteImageInfo>();
- var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
- foreach (KeyType keyType in keyTypes)
+ var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
+ .ConfigureAwait(false);
+ await foreach (KeyType keyType in allowedKeyTypes)
{
var imageQuery = new ImagesQuery
{
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
index b3641dc9f..df48629d0 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbSeriesProvider.cs
@@ -247,10 +247,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
Name = tvdbTitles.FirstOrDefault(),
ProductionYear = firstAired.Year,
- SearchProviderName = Name,
- ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
+ SearchProviderName = Name
};
+ if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
+ {
+ // Results from their Search endpoints already include the /banners/ part in the url, because reasons...
+ remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
+ }
+
try
{
var seriesSesult =
@@ -365,10 +370,14 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
Type = PersonType.Actor,
Name = (actor.Name ?? string.Empty).Trim(),
Role = actor.Role,
- ImageUrl = TvdbUtils.BannerUrl + actor.Image,
SortOrder = actor.SortOrder
};
+ if (!string.IsNullOrEmpty(actor.Image))
+ {
+ personInfo.ImageUrl = TvdbUtils.BannerUrl + actor.Image;
+ }
+
if (!string.IsNullOrWhiteSpace(personInfo.Name))
{
result.AddPerson(personInfo);
diff --git a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
index 3f71041b2..37a8d04a6 100644
--- a/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/TheTvdb/TvdbUtils.cs
@@ -9,7 +9,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
{
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
- public const string BannerUrl = TvdbBaseUrl + "banners/";
+ public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
+ public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
public static ImageType GetImageTypeFromKeyType(string keyType)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
index 5e2b83294..74870c999 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs
@@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
using (HttpResponseInfo response = await GetMovieDbResponse(new HttpRequestOptions
{
- Url = string.Format(TmdbConfigUrl, TmdbUtils.ApiKey),
+ Url = string.Format(CultureInfo.InvariantCulture, TmdbConfigUrl, TmdbUtils.ApiKey),
CancellationToken = cancellationToken,
AcceptHeader = TmdbUtils.AcceptHeader
}).ConfigureAwait(false))
@@ -245,7 +245,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
preferredLanguage = "alllang";
}
- var filename = string.Format("all-{0}.json", preferredLanguage);
+ var filename = string.Format(CultureInfo.InvariantCulture, "all-{0}.json", preferredLanguage);
return Path.Combine(path, filename);
}
@@ -276,7 +276,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
languages.Add("en");
}
- return string.Join(",", languages.ToArray());
+ return string.Join(",", languages);
}
public static string NormalizeLanguage(string language)
@@ -321,11 +321,11 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
/// <returns>Task{CompleteMovieData}.</returns>
internal async Task<MovieResult> FetchMainResult(string id, bool isTmdbId, string language, CancellationToken cancellationToken)
{
- var url = string.Format(GetMovieInfo3, id, TmdbUtils.ApiKey);
+ var url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey);
if (!string.IsNullOrEmpty(language))
{
- url += string.Format("&language={0}", NormalizeLanguage(language));
+ url += string.Format(CultureInfo.InvariantCulture, "&language={0}", NormalizeLanguage(language));
// Get images in english and with no language
url += "&include_image_language=" + GetImageLanguagesParam(language);
@@ -377,7 +377,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
{
_logger.LogInformation("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
- url = string.Format(GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
+ url = string.Format(CultureInfo.InvariantCulture, GetMovieInfo3, id, TmdbUtils.ApiKey) + "&language=en";
if (!string.IsNullOrEmpty(language))
{
diff --git a/debian/changelog b/debian/changelog
index 35fb65957..d38d03805 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+jellyfin-server (10.6.0-2) unstable; urgency=medium
+
+ * Fix upgrade bug
+
+ -- Joshua Boniface <joshua@boniface.me> Sun, 19 Jul 22:47:27 -0400
+
jellyfin-server (10.6.0-1) unstable; urgency=medium
* Forthcoming stable release
diff --git a/debian/control b/debian/control
index 896d8286b..39c2aa055 100644
--- a/debian/control
+++ b/debian/control
@@ -15,9 +15,8 @@ Vcs-Git: https://github.org/jellyfin/jellyfin.git
Vcs-Browser: https://github.org/jellyfin/jellyfin
Package: jellyfin-server
-Replaces: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
-Breaks: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
-Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
+Replaces: jellyfin (<<10.6.0)
+Breaks: jellyfin (<<10.6.0)
Architecture: any
Depends: at,
libsqlite3-0,
diff --git a/deployment/build.windows.amd64 b/deployment/build.windows.amd64
index 3fabc2cac..bd5dc6438 100755
--- a/deployment/build.windows.amd64
+++ b/deployment/build.windows.amd64
@@ -8,8 +8,7 @@ set -o xtrace
# Version variables
NSSM_VERSION="nssm-2.24-101-g897c7ad"
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
-FFMPEG_VERSION="ffmpeg-4.2.1-win64-static"
-FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
+FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip";
# Move to source directory
pushd ${SOURCE_DIR}
@@ -29,12 +28,11 @@ dotnet publish Jellyfin.Server --configuration Release --self-contained --runtim
# Prepare addins
addin_build_dir="$( mktemp -d )"
wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
-wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
+wget ${FFMPEG_URL} -O ${addin_build_dir}/jellyfin-ffmpeg.zip
unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
-unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${output_dir}/ffmpeg.exe
-cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe ${output_dir}/ffprobe.exe
+unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmpeg
+cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir}
rm -rf ${addin_build_dir}
# Prepare scripts
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index 100f95424..132050e7b 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -13,15 +13,15 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.12.0" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
- <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
+ <PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
- <PackageReference Include="Moq" Version="4.14.4" />
+ <PackageReference Include="Moq" Version="4.14.5" />
</ItemGroup>
<!-- Code Analyzers -->
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
index af29fec87..9eb601edf 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTests.cs
@@ -17,6 +17,7 @@ namespace Jellyfin.MediaEncoding.Tests
}
[Theory]
+ [InlineData(EncoderValidatorTestsData.FFmpegV43Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV421Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV42Output, true)]
[InlineData(EncoderValidatorTestsData.FFmpegV414Output, true)]
@@ -32,6 +33,7 @@ namespace Jellyfin.MediaEncoding.Tests
{
public IEnumerator<object?[]> GetEnumerator()
{
+ yield return new object?[] { EncoderValidatorTestsData.FFmpegV43Output, new Version(4, 3) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV421Output, new Version(4, 2, 1) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV42Output, new Version(4, 2) };
yield return new object?[] { EncoderValidatorTestsData.FFmpegV414Output, new Version(4, 1, 4) };
diff --git a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
index c46c9578b..f5ff3d723 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
+++ b/tests/Jellyfin.MediaEncoding.Tests/EncoderValidatorTestsData.cs
@@ -2,6 +2,18 @@ namespace Jellyfin.MediaEncoding.Tests
{
internal static class EncoderValidatorTestsData
{
+ public const string FFmpegV43Output = @"ffmpeg version 4.3 Copyright (c) 2000-2020 the FFmpeg developers
+built with gcc 7 (Ubuntu 7.5.0-3ubuntu1~18.04)
+configuration: --prefix=/usr/lib/jellyfin-ffmpeg --target-os=linux --disable-doc --disable-ffplay --disable-shared --disable-libxcb --disable-vdpau --disable-sdl2 --disable-xlib --enable-gpl --enable-version3 --enable-static --enable-libfontconfig --enable-fontconfig --enable-gmp --enable-gnutls --enable-libass --enable-libbluray --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libmp3lame --enable-libopus --enable-libtheora --enable-libvorbis --enable-libwebp --enable-libx264 --enable-libx265 --enable-libzvbi --arch=amd64 --enable-amf --enable-nvenc --enable-nvdec --enable-vaapi --enable-opencl
+libavutil 56. 51.100 / 56. 51.100
+libavcodec 58. 91.100 / 58. 91.100
+libavformat 58. 45.100 / 58. 45.100
+libavdevice 58. 10.100 / 58. 10.100
+libavfilter 7. 85.100 / 7. 85.100
+libswscale 5. 7.100 / 5. 7.100
+libswresample 3. 7.100 / 3. 7.100
+libpostproc 55. 7.100 / 55. 7.100";
+
public const string FFmpegV421Output = @"ffmpeg version 4.2.1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 9.1.1 (GCC) 20190807
configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index dd53c9902..03187f4b9 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -14,9 +14,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.12.0" />
+ <PackageReference Include="AutoFixture" Version="4.13.0" />
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
- <PackageReference Include="Moq" Version="4.14.4" />
+ <PackageReference Include="Moq" Version="4.14.5" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
<PackageReference Include="coverlet.collector" Version="1.3.0" />
diff --git a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
index a767c0039..8309faebd 100644
--- a/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
+++ b/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj
@@ -8,7 +8,7 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.5" />
+ <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
diff --git a/windows/build-jellyfin.ps1 b/windows/build-jellyfin.ps1
index c762137a7..b76a8e0bb 100644
--- a/windows/build-jellyfin.ps1
+++ b/windows/build-jellyfin.ps1
@@ -47,7 +47,7 @@ function Install-FFMPEG {
param(
[string]$ResolvedInstallLocation,
[string]$Architecture,
- [string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared"
+ [string]$FFMPEGVersionX86 = "ffmpeg-4.3-win32-shared"
)
Write-Verbose "Checking Architecture"
if($Architecture -notin @('x86','x64')){