mirror of https://github.com/jellyfin/jellyfin.git
Merge pull request from jellyfin/master
This commit is contained in:
commit
83a344b627
|
@ -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,7 @@ 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace Emby.Dlna.Profiles
|
|||
},
|
||||
};
|
||||
|
||||
ResponseProfiles = new ResponseProfile[] { };
|
||||
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace Emby.Dlna.Profiles
|
|||
}
|
||||
};
|
||||
|
||||
ResponseProfiles = new ResponseProfile[] { };
|
||||
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@ namespace Emby.Dlna.Profiles
|
|||
}
|
||||
};
|
||||
|
||||
ResponseProfiles = new ResponseProfile[] { };
|
||||
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace Emby.Dlna.Profiles
|
|||
},
|
||||
};
|
||||
|
||||
ResponseProfiles = new ResponseProfile[] { };
|
||||
ResponseProfiles = System.Array.Empty<ResponseProfile>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -566,10 +564,8 @@ namespace Emby.Server.Implementations
|
|||
serviceCollection.AddTransient(provider => new Lazy<IDtoService>(provider.GetRequiredService<IDtoService>));
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependency here so that Lazy<T> isn't required
|
||||
// TODO: Add StartupOptions.FFmpegPath to IConfiguration and remove this custom activation
|
||||
serviceCollection.AddTransient(provider => new Lazy<EncodingHelper>(provider.GetRequiredService<EncodingHelper>));
|
||||
serviceCollection.AddSingleton<IMediaEncoder>(provider =>
|
||||
ActivatorUtilities.CreateInstance<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(provider, _startupOptions.FFmpegPath ?? string.Empty));
|
||||
serviceCollection.AddSingleton<IMediaEncoder, MediaBrowser.MediaEncoding.Encoder.MediaEncoder>();
|
||||
|
||||
// TODO: Refactor to eliminate the circular dependencies here so that Lazy<T> isn't required
|
||||
serviceCollection.AddTransient(provider => new Lazy<ILibraryMonitor>(provider.GetRequiredService<ILibraryMonitor>));
|
||||
|
@ -873,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)
|
||||
{
|
||||
|
@ -1155,7 +1156,7 @@ namespace Emby.Server.Implementations
|
|||
return null;
|
||||
}
|
||||
|
||||
return GetLocalApiUrl(addresses.First());
|
||||
return GetLocalApiUrl(addresses[0]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1228,7 +1229,7 @@ namespace Emby.Server.Implementations
|
|||
var addresses = ServerConfigurationManager
|
||||
.Configuration
|
||||
.LocalNetworkAddresses
|
||||
.Select(NormalizeConfiguredLocalAddress)
|
||||
.Select(x => NormalizeConfiguredLocalAddress(x))
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
|
||||
|
@ -1249,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);
|
||||
|
||||
|
@ -1264,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))
|
||||
|
|
|
@ -363,60 +363,4 @@ namespace Emby.Server.Implementations.Collections
|
|||
return results.Values;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The collection manager entry point.
|
||||
/// </summary>
|
||||
public sealed class CollectionManagerEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly CollectionManager _collectionManager;
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly ILogger<CollectionManagerEntryPoint> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionManagerEntryPoint"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collectionManager">The collection manager.</param>
|
||||
/// <param name="config">The server configuration manager.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public CollectionManagerEntryPoint(
|
||||
ICollectionManager collectionManager,
|
||||
IServerConfigurationManager config,
|
||||
ILogger<CollectionManagerEntryPoint> logger)
|
||||
{
|
||||
_collectionManager = (CollectionManager)collectionManager;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RunAsync()
|
||||
{
|
||||
if (!_config.Configuration.CollectionsUpgraded && _config.Configuration.IsStartupWizardCompleted)
|
||||
{
|
||||
var path = _collectionManager.GetCollectionsFolderPath();
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collectionManager.EnsureLibraryFolder(path, true).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error creating camera uploads library");
|
||||
}
|
||||
|
||||
_config.Configuration.CollectionsUpgraded = true;
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
// Nothing to dispose
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -109,7 +109,6 @@ namespace Emby.Server.Implementations.Configuration
|
|||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(Configuration.CertificatePath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!File.Exists(newPath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
|
@ -133,7 +132,6 @@ namespace Emby.Server.Implementations.Configuration
|
|||
if (!string.IsNullOrWhiteSpace(newPath)
|
||||
&& !string.Equals(Configuration.MetadataPath, newPath, StringComparison.Ordinal))
|
||||
{
|
||||
// Validate
|
||||
if (!Directory.Exists(newPath))
|
||||
{
|
||||
throw new DirectoryNotFoundException(
|
||||
|
@ -146,60 +144,5 @@ namespace Emby.Server.Implementations.Configuration
|
|||
EnsureWriteAccess(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all configuration values to their optimal values.
|
||||
/// </summary>
|
||||
/// <returns>If the configuration changed.</returns>
|
||||
public bool SetOptimalValues()
|
||||
{
|
||||
var config = Configuration;
|
||||
|
||||
var changed = false;
|
||||
|
||||
if (!config.EnableCaseSensitiveItemIds)
|
||||
{
|
||||
config.EnableCaseSensitiveItemIds = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.SkipDeserializationForBasicTypes)
|
||||
{
|
||||
config.SkipDeserializationForBasicTypes = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableSimpleArtistDetection)
|
||||
{
|
||||
config.EnableSimpleArtistDetection = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableNormalizedItemByNameIds)
|
||||
{
|
||||
config.EnableNormalizedItemByNameIds = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.DisableLiveTvChannelUserDataName)
|
||||
{
|
||||
config.DisableLiveTvChannelUserDataName = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.EnableNewOmdbSupport)
|
||||
{
|
||||
config.EnableNewOmdbSupport = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!config.CollectionsUpgraded)
|
||||
{
|
||||
config.CollectionsUpgraded = true;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IPNetwork2" Version="2.5.211" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.4.3" />
|
||||
<PackageReference Include="Jellyfin.XmlTv" Version="10.6.0-pre1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.2.0" />
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
|
@ -13,19 +14,28 @@ namespace Emby.Server.Implementations.Library
|
|||
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IServerApplicationPaths _serverApplicationPaths;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CoreResolutionIgnoreRule"/> class.
|
||||
/// </summary>
|
||||
/// <param name="libraryManager">The library manager.</param>
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager)
|
||||
/// <param name="serverApplicationPaths">The server application paths.</param>
|
||||
public CoreResolutionIgnoreRule(ILibraryManager libraryManager, IServerApplicationPaths serverApplicationPaths)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_serverApplicationPaths = serverApplicationPaths;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
|
||||
{
|
||||
// Don't ignore application folders
|
||||
if (fileInfo.FullName.Contains(_serverApplicationPaths.RootFolderPath, StringComparison.InvariantCulture))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't ignore top level folders
|
||||
if (fileInfo.IsDirectory && parent is AggregateFolder)
|
||||
{
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using DotNet.Globbing;
|
||||
|
||||
|
@ -11,7 +14,7 @@ namespace Emby.Server.Implementations.Library
|
|||
/// <summary>
|
||||
/// Files matching these glob patterns will be ignored.
|
||||
/// </summary>
|
||||
public static readonly string[] Patterns = new string[]
|
||||
private static readonly string[] _patterns =
|
||||
{
|
||||
"**/small.jpg",
|
||||
"**/albumart.jpg",
|
||||
|
@ -19,32 +22,51 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
// Directories
|
||||
"**/metadata/**",
|
||||
"**/metadata",
|
||||
"**/ps3_update/**",
|
||||
"**/ps3_update",
|
||||
"**/ps3_vprm/**",
|
||||
"**/ps3_vprm",
|
||||
"**/extrafanart/**",
|
||||
"**/extrafanart",
|
||||
"**/extrathumbs/**",
|
||||
"**/extrathumbs",
|
||||
"**/.actors/**",
|
||||
"**/.actors",
|
||||
"**/.wd_tv/**",
|
||||
"**/.wd_tv",
|
||||
"**/lost+found/**",
|
||||
"**/lost+found",
|
||||
|
||||
// WMC temp recording directories that will constantly be written to
|
||||
"**/TempRec/**",
|
||||
"**/TempRec",
|
||||
"**/TempSBE/**",
|
||||
"**/TempSBE",
|
||||
|
||||
// Synology
|
||||
"**/eaDir/**",
|
||||
"**/eaDir",
|
||||
"**/@eaDir/**",
|
||||
"**/@eaDir",
|
||||
"**/#recycle/**",
|
||||
"**/#recycle",
|
||||
|
||||
// Qnap
|
||||
"**/@Recycle/**",
|
||||
"**/@Recycle",
|
||||
"**/.@__thumb/**",
|
||||
"**/.@__thumb",
|
||||
"**/$RECYCLE.BIN/**",
|
||||
"**/$RECYCLE.BIN",
|
||||
"**/System Volume Information/**",
|
||||
"**/System Volume Information",
|
||||
"**/.grab/**",
|
||||
"**/.grab",
|
||||
|
||||
// Unix hidden files and directories
|
||||
"**/.*/**",
|
||||
"**/.*",
|
||||
|
||||
// thumbs.db
|
||||
"**/thumbs.db",
|
||||
|
@ -56,19 +78,31 @@ namespace Emby.Server.Implementations.Library
|
|||
|
||||
private static readonly GlobOptions _globOptions = new GlobOptions
|
||||
{
|
||||
Evaluation = {
|
||||
Evaluation =
|
||||
{
|
||||
CaseInsensitive = true
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly Glob[] _globs = Patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
|
||||
private static readonly Glob[] _globs = _patterns.Select(p => Glob.Parse(p, _globOptions)).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the supplied path should be ignored.
|
||||
/// </summary>
|
||||
public static bool ShouldIgnore(string path)
|
||||
/// <param name="path">The path to test.</param>
|
||||
/// <returns>Whether to ignore the path.</returns>
|
||||
public static bool ShouldIgnore(ReadOnlySpan<char> path)
|
||||
{
|
||||
return _globs.Any(g => g.IsMatch(path));
|
||||
int len = _globs.Length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
if (_globs[i].IsMatch(path))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -514,8 +514,8 @@ namespace Emby.Server.Implementations.Library
|
|||
return key.GetMD5();
|
||||
}
|
||||
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null, bool allowIgnorePath = true)
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent, allowIgnorePath: allowIgnorePath);
|
||||
public BaseItem ResolvePath(FileSystemMetadata fileInfo, Folder parent = null)
|
||||
=> ResolvePath(fileInfo, new DirectoryService(_fileSystem), null, parent);
|
||||
|
||||
private BaseItem ResolvePath(
|
||||
FileSystemMetadata fileInfo,
|
||||
|
@ -523,8 +523,7 @@ namespace Emby.Server.Implementations.Library
|
|||
IItemResolver[] resolvers,
|
||||
Folder parent = null,
|
||||
string collectionType = null,
|
||||
LibraryOptions libraryOptions = null,
|
||||
bool allowIgnorePath = true)
|
||||
LibraryOptions libraryOptions = null)
|
||||
{
|
||||
if (fileInfo == null)
|
||||
{
|
||||
|
@ -548,7 +547,7 @@ namespace Emby.Server.Implementations.Library
|
|||
};
|
||||
|
||||
// Return null if ignore rules deem that we should do so
|
||||
if (allowIgnorePath && IgnoreFile(args.FileInfo, args.Parent))
|
||||
if (IgnoreFile(args.FileInfo, args.Parent))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -713,7 +712,7 @@ namespace Emby.Server.Implementations.Library
|
|||
Directory.CreateDirectory(rootFolderPath);
|
||||
|
||||
var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ??
|
||||
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath), allowIgnorePath: false))
|
||||
((Folder) ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath)))
|
||||
.DeepCopy<Folder, AggregateFolder>();
|
||||
|
||||
// In case program data folder was moved
|
||||
|
@ -795,7 +794,7 @@ namespace Emby.Server.Implementations.Library
|
|||
if (tmpItem == null)
|
||||
{
|
||||
_logger.LogDebug("Creating new userRootFolder with DeepCopy");
|
||||
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath), allowIgnorePath: false)).DeepCopy<Folder, UserRootFolder>();
|
||||
tmpItem = ((Folder)ResolvePath(_fileSystem.GetDirectoryInfo(userRootPath))).DeepCopy<Folder, UserRootFolder>();
|
||||
}
|
||||
|
||||
// In case program data folder was moved
|
||||
|
@ -1894,9 +1893,19 @@ namespace Emby.Server.Implementations.Library
|
|||
}
|
||||
}
|
||||
|
||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||
image.Width = size.Width;
|
||||
image.Height = size.Height;
|
||||
try
|
||||
{
|
||||
ImageDimensions size = _imageProcessor.GetImageDimensions(item, image);
|
||||
image.Width = size.Width;
|
||||
image.Height = size.Height;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Cannnot get image dimensions for {0}", image.Path);
|
||||
image.Width = 0;
|
||||
image.Height = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"Albums": "ألبومات",
|
||||
"Albums": "البومات",
|
||||
"AppDeviceValues": "تطبيق: {0}, جهاز: {1}",
|
||||
"Application": "تطبيق",
|
||||
"Artists": "الفنانين",
|
||||
|
@ -14,7 +14,7 @@
|
|||
"FailedLoginAttemptWithUserName": "عملية تسجيل الدخول فشلت من {0}",
|
||||
"Favorites": "المفضلة",
|
||||
"Folders": "المجلدات",
|
||||
"Genres": "الأنواع",
|
||||
"Genres": "التضنيفات",
|
||||
"HeaderAlbumArtists": "فناني الألبومات",
|
||||
"HeaderCameraUploads": "تحميلات الكاميرا",
|
||||
"HeaderContinueWatching": "استئناف",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"NotificationOptionAudioPlayback": "بدأ تشغيل المقطع الصوتي",
|
||||
"NotificationOptionAudioPlaybackStopped": "تم إيقاف تشغيل المقطع الصوتي",
|
||||
"NotificationOptionCameraImageUploaded": "تم رفع صورة الكاميرا",
|
||||
"NotificationOptionInstallationFailed": "فشل في التثبيت",
|
||||
"NotificationOptionInstallationFailed": "فشل التثبيت",
|
||||
"NotificationOptionNewLibraryContent": "تم إضافة محتوى جديد",
|
||||
"NotificationOptionPluginError": "فشل في البرنامج المضاف",
|
||||
"NotificationOptionPluginInstalled": "تم تثبيت الملحق",
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
"ItemAddedWithName": "{0} fue agregado a la biblioteca",
|
||||
"ItemRemovedWithName": "{0} fue removido de la biblioteca",
|
||||
"LabelIpAddressValue": "Dirección IP: {0}",
|
||||
"LabelRunningTimeValue": "Duración: {0}",
|
||||
"LabelRunningTimeValue": "Tiempo de reproducción: {0}",
|
||||
"Latest": "Recientes",
|
||||
"MessageApplicationUpdated": "El servidor Jellyfin ha sido actualizado",
|
||||
"MessageApplicationUpdatedTo": "El servidor Jellyfin ha sido actualizado a {0}",
|
||||
|
|
|
@ -57,5 +57,7 @@
|
|||
"HeaderCameraUploads": "कॅमेरा अपलोड",
|
||||
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
|
||||
"Application": "अॅप्लिकेशन",
|
||||
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}"
|
||||
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}",
|
||||
"Collections": "संग्रह",
|
||||
"ChapterNameValue": "धडा {0}"
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Emby.Server.Implementations.Net
|
|||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
try
|
||||
{
|
||||
retVal.EnableBroadcast = true;
|
||||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
|
||||
|
||||
|
@ -46,6 +47,7 @@ namespace Emby.Server.Implementations.Net
|
|||
var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
|
||||
try
|
||||
{
|
||||
retVal.EnableBroadcast = true;
|
||||
retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
|
||||
|
||||
|
@ -112,6 +114,7 @@ namespace Emby.Server.Implementations.Net
|
|||
|
||||
try
|
||||
{
|
||||
retVal.EnableBroadcast = true;
|
||||
// retVal.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
|
||||
retVal.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicastTimeToLive);
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -502,7 +502,8 @@ namespace Emby.Server.Implementations.Session
|
|||
Client = appName,
|
||||
DeviceId = deviceId,
|
||||
ApplicationVersion = appVersion,
|
||||
Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture)
|
||||
Id = key.GetMD5().ToString("N", CultureInfo.InvariantCulture),
|
||||
ServerId = _appHost.SystemId
|
||||
};
|
||||
|
||||
var username = user?.Username;
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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!");
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace Jellyfin.Api.Controllers
|
|||
public void CompleteWizard()
|
||||
{
|
||||
_config.Configuration.IsStartupWizardCompleted = true;
|
||||
_config.SetOptimalValues();
|
||||
_config.SaveConfiguration();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Entities;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
|
@ -117,29 +116,5 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordHash = _cryptographyProvider.CreatePasswordHash(newPassword).ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordHash))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordHash));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordHash;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.IsNullOrEmpty(user.EasyPassword)
|
||||
? null
|
||||
: Hex.Encode(PasswordHash.Parse(user.EasyPassword).Hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,17 +34,5 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ChangeEasyPassword(User user, string newPassword, string newPasswordHash)
|
||||
{
|
||||
// Nothing here
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetEasyPasswordHash(User user)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ using Jellyfin.Data.Entities;
|
|||
using Jellyfin.Data.Enums;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Cryptography;
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Authentication;
|
||||
using MediaBrowser.Controller.Drawing;
|
||||
|
@ -22,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
|
||||
|
@ -85,7 +87,19 @@ 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);
|
||||
|
@ -98,7 +112,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/>
|
||||
|
@ -109,9 +129,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/>
|
||||
|
@ -127,7 +152,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
throw new ArgumentException("Invalid username", nameof(newName));
|
||||
}
|
||||
|
||||
if (user.Username.Equals(newName, StringComparison.Ordinal))
|
||||
if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new ArgumentException("The new and old names must be different.");
|
||||
}
|
||||
|
@ -149,7 +174,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();
|
||||
}
|
||||
|
@ -157,7 +182,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);
|
||||
|
@ -171,7 +196,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;
|
||||
|
@ -192,15 +217,20 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DeleteUser(User user)
|
||||
public void DeleteUser(Guid 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 ArgumentNullException(nameof(user));
|
||||
throw new ResourceNotFoundException(nameof(userId));
|
||||
}
|
||||
|
||||
var dbContext = _dbProvider.CreateContext();
|
||||
|
||||
if (dbContext.Users.Find(user.Id) == null)
|
||||
{
|
||||
throw new ArgumentException(string.Format(
|
||||
|
@ -226,9 +256,18 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
CultureInfo.InvariantCulture,
|
||||
"The user '{0}' cannot be deleted because there must be at least one admin user in the system.",
|
||||
user.Username),
|
||||
nameof(user));
|
||||
nameof(userId));
|
||||
}
|
||||
|
||||
// Clear all entities related to the user from the database.
|
||||
if (user.ProfileImage != null)
|
||||
{
|
||||
dbContext.Remove(user.ProfileImage);
|
||||
}
|
||||
|
||||
dbContext.RemoveRange(user.Permissions);
|
||||
dbContext.RemoveRange(user.Preferences);
|
||||
dbContext.RemoveRange(user.AccessSchedules);
|
||||
dbContext.Users.Remove(user);
|
||||
dbContext.SaveChanges();
|
||||
OnUserDeleted?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
|
@ -263,7 +302,17 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
/// <inheritdoc/>
|
||||
public void ChangeEasyPassword(User user, string newPassword, string? newPasswordSha1)
|
||||
{
|
||||
GetAuthenticationProvider(user).ChangeEasyPassword(user, newPassword, newPasswordSha1);
|
||||
if (newPassword != null)
|
||||
{
|
||||
newPasswordSha1 = _cryptoProvider.CreatePasswordHash(newPassword).ToString();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newPasswordSha1))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newPasswordSha1));
|
||||
}
|
||||
|
||||
user.EasyPassword = newPasswordSha1;
|
||||
UpdateUser(user);
|
||||
|
||||
OnUserPasswordChanged?.Invoke(this, new GenericEventArgs<User>(user));
|
||||
|
@ -361,7 +410,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;
|
||||
|
||||
|
@ -389,8 +438,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)
|
||||
{
|
||||
|
@ -527,7 +575,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())
|
||||
{
|
||||
|
@ -590,7 +638,14 @@ namespace Jellyfin.Server.Implementations.Users
|
|||
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!");
|
||||
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;
|
||||
|
@ -679,7 +734,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();
|
||||
}
|
||||
|
|
|
@ -66,8 +66,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>();
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -101,6 +101,11 @@ namespace Jellyfin.Server
|
|||
config.Add(UdpServer.AddressOverrideConfigKey, PublishedServerUrl.ToString());
|
||||
}
|
||||
|
||||
if (FFmpegPath != null)
|
||||
{
|
||||
config.Add(ConfigurationExtensions.FfmpegPathKey, FFmpegPath);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
|
|
|
@ -11,93 +11,66 @@ using Microsoft.Extensions.Logging;
|
|||
|
||||
namespace MediaBrowser.Api.SyncPlay
|
||||
{
|
||||
[Route("/SyncPlay/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Group id.
|
||||
/// </summary>
|
||||
/// <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/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/ListGroups", "POST", Summary = "List SyncPlay groups")]
|
||||
[Route("/SyncPlay/List", "GET", Summary = "List SyncPlay groups")]
|
||||
[Authenticated]
|
||||
public class SyncPlayListGroups : IReturnVoid
|
||||
public class SyncPlayList : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the filter item id.
|
||||
/// </summary>
|
||||
/// <value>The filter item id.</value>
|
||||
[ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
[ApiMember(Name = "FilterItemId", Description = "Filter by item id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
|
||||
public string FilterItemId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/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 = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
[ApiMember(Name = "PositionTicks", IsRequired = true, DataType = "long", ParameterType = "query", Verb = "POST")]
|
||||
public long PositionTicks { get; set; }
|
||||
}
|
||||
|
||||
[Route("/SyncPlay/{SessionId}/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
|
||||
{
|
||||
[ApiMember(Name = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the date used to pin PositionTicks in time.
|
||||
/// </summary>
|
||||
|
@ -109,20 +82,17 @@ 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/{SessionId}/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 = "SessionId", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
|
||||
public string SessionId { get; set; }
|
||||
|
||||
[ApiMember(Name = "Ping", IsRequired = true, DataType = "double", ParameterType = "query", Verb = "POST")]
|
||||
public double Ping { get; set; }
|
||||
}
|
||||
|
@ -158,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);
|
||||
|
@ -168,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);
|
||||
|
@ -201,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);
|
||||
|
@ -212,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;
|
||||
|
@ -229,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()
|
||||
|
@ -243,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()
|
||||
|
@ -257,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()
|
||||
|
@ -272,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
|
||||
};
|
||||
|
@ -288,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);
|
||||
|
|
|
@ -365,15 +365,8 @@ namespace MediaBrowser.Api
|
|||
|
||||
public Task DeleteAsync(DeleteUser request)
|
||||
{
|
||||
var user = _userManager.GetUserById(request.Id);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ResourceNotFoundException("User not found");
|
||||
}
|
||||
|
||||
_sessionMananger.RevokeUserTokens(user.Id, null);
|
||||
_userManager.DeleteUser(user);
|
||||
_userManager.DeleteUser(request.Id);
|
||||
_sessionMananger.RevokeUserTokens(request.Id, null);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ namespace MediaBrowser.Controller.Authentication
|
|||
Task<ProviderAuthenticationResult> Authenticate(string username, string password);
|
||||
bool HasPassword(User user);
|
||||
Task ChangePassword(User user, string newPassword);
|
||||
void ChangeEasyPassword(User user, string newPassword, string newPasswordHash);
|
||||
string GetEasyPasswordHash(User user);
|
||||
}
|
||||
|
||||
public interface IRequiresResolvedUser
|
||||
|
|
|
@ -19,7 +19,5 @@ namespace MediaBrowser.Controller.Configuration
|
|||
/// </summary>
|
||||
/// <value>The configuration.</value>
|
||||
ServerConfiguration Configuration { get; }
|
||||
|
||||
bool SetOptimalValues();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -613,7 +613,7 @@ namespace MediaBrowser.Controller.Entities
|
|||
{
|
||||
if (!IsFileProtocol)
|
||||
{
|
||||
return new string[] { };
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return new[] { Path };
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -23,6 +23,11 @@ namespace MediaBrowser.Controller.Extensions
|
|||
/// </summary>
|
||||
public const string FfmpegAnalyzeDurationKey = "FFmpeg:analyzeduration";
|
||||
|
||||
/// <summary>
|
||||
/// The key for the FFmpeg path option.
|
||||
/// </summary>
|
||||
public const string FfmpegPathKey = "ffmpeg";
|
||||
|
||||
/// <summary>
|
||||
/// The key for a setting that indicates whether playlists should allow duplicate entries.
|
||||
/// </summary>
|
||||
|
|
|
@ -30,12 +30,10 @@ namespace MediaBrowser.Controller.Library
|
|||
/// </summary>
|
||||
/// <param name="fileInfo">The file information.</param>
|
||||
/// <param name="parent">The parent.</param>
|
||||
/// <param name="allowIgnorePath">Allow the path to be ignored.</param>
|
||||
/// <returns>BaseItem.</returns>
|
||||
BaseItem ResolvePath(
|
||||
FileSystemMetadata fileInfo,
|
||||
Folder parent = null,
|
||||
bool allowIgnorePath = true);
|
||||
Folder parent = null);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a set of files into a list of BaseItem.
|
||||
|
|
|
@ -111,8 +111,8 @@ namespace MediaBrowser.Controller.Library
|
|||
/// <summary>
|
||||
/// Deletes the specified user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to be deleted.</param>
|
||||
void DeleteUser(User user);
|
||||
/// <param name="userId">The id of the user to be deleted.</param>
|
||||
void DeleteUser(Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// Resets the password.
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Library
|
|||
};
|
||||
}
|
||||
|
||||
return new DayOfWeek[] { };
|
||||
return Array.Empty<DayOfWeek>();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
@ -483,7 +483,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
{
|
||||
if (isQsvDecoder)
|
||||
{
|
||||
arg.Append("-hwaccel qsv ");
|
||||
arg.Append("-hwaccel qsv -init_hw_device qsv=hw ");
|
||||
}
|
||||
// While using SW decoder
|
||||
else
|
||||
|
@ -1351,7 +1351,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
transcoderChannelLimit = 6;
|
||||
}
|
||||
|
||||
var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec);
|
||||
var isTranscodingAudio = !IsCopyCodec(codec);
|
||||
|
||||
int? resultChannels = state.GetRequestedAudioChannels(codec);
|
||||
if (isTranscodingAudio)
|
||||
|
@ -1757,7 +1757,7 @@ 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);
|
||||
|
||||
if (!videoWidth.HasValue
|
||||
|| outputWidth != videoWidth.Value
|
||||
|
@ -1765,17 +1765,19 @@ 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",
|
||||
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
|
||||
outputWidth,
|
||||
outputHeight));
|
||||
}
|
||||
else
|
||||
{
|
||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "scale_{0}=format=nv12", vaapi_or_qsv));
|
||||
// set w=0:h=0 for vpp_qsv to keep the original dimensions, otherwise it will fail.
|
||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "{0}format=nv12", qsv_or_vaapi ? "vpp_qsv=w=0:h=0:" : "scale_vaapi="));
|
||||
}
|
||||
}
|
||||
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
|
||||
|
@ -2262,7 +2264,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
flags.Add("+ignidx");
|
||||
}
|
||||
|
||||
if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
||||
if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
|
||||
{
|
||||
flags.Add("+genpts");
|
||||
}
|
||||
|
@ -2521,21 +2523,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 +2546,16 @@ 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 = !string.IsNullOrEmpty(videoStream.Profile)
|
||||
&& (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase) || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// 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)
|
||||
|
@ -3000,7 +3008,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 +3110,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||
|
||||
var args = "-codec:a:0 " + codec;
|
||||
|
||||
if (EncodingHelper.IsCopyCodec(codec))
|
||||
if (IsCopyCodec(codec))
|
||||
{
|
||||
return args;
|
||||
}
|
||||
|
|
|
@ -1,15 +1,45 @@
|
|||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an identifier for an external provider.
|
||||
/// </summary>
|
||||
public interface IExternalId
|
||||
{
|
||||
string Name { get; }
|
||||
/// <summary>
|
||||
/// Gets the display name of the provider associated with this ID type.
|
||||
/// </summary>
|
||||
string ProviderName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique key to distinguish this provider/type pair. This should be unique across providers.
|
||||
/// </summary>
|
||||
// TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique.
|
||||
string Key { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the specific media type for this id. This is used to distinguish between the different
|
||||
/// external id types for providers with multiple ids.
|
||||
/// A null value indicates there is no specific media type associated with the external id, or this is the
|
||||
/// default id for the external provider so there is no need to specify a type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used along with the <see cref="ProviderName"/> to localize the external id on the client.
|
||||
/// </remarks>
|
||||
ExternalIdMediaType? Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the URL format string for this id.
|
||||
/// </summary>
|
||||
string UrlFormatString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this id supports a given item type.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>True if this item is supported, otherwise false.</returns>
|
||||
bool Supports(IHasProviderIds item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,6 +108,12 @@ namespace MediaBrowser.Controller.Session
|
|||
/// <value>The name of the device.</value>
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the device.
|
||||
/// </summary>
|
||||
/// <value>The type of the device.</value>
|
||||
public string DeviceType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the now playing item.
|
||||
/// </summary>
|
||||
|
@ -215,8 +221,17 @@ namespace MediaBrowser.Controller.Session
|
|||
|
||||
public string PlaylistItemId { get; set; }
|
||||
|
||||
public string ServerId { get; set; }
|
||||
|
||||
public string UserPrimaryImageTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the supported commands.
|
||||
/// </summary>
|
||||
/// <value>The supported commands.</value>
|
||||
public string[] SupportedCommands
|
||||
=> Capabilities == null ? Array.Empty<string>() : Capabilities.SupportedCommands;
|
||||
|
||||
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
|
||||
{
|
||||
var controllers = SessionControllers.ToList();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
|
|||
var id = info.Key + "Id";
|
||||
if (!_validProviderIds.ContainsKey(id))
|
||||
{
|
||||
_validProviderIds.Add(id, info.Key);
|
||||
_validProviderIds.Add(id, info.Key!);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace MediaBrowser.MediaEncoding.BdInfo
|
|||
|
||||
var outputStream = new BlurayDiscInfo
|
||||
{
|
||||
MediaStreams = new MediaStream[] { }
|
||||
MediaStreams = Array.Empty<MediaStream>()
|
||||
};
|
||||
|
||||
if (playlist == null)
|
||||
|
|
|
@ -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) },
|
||||
|
|
|
@ -21,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
|
|||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
|
@ -46,7 +47,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
private readonly object _runningProcessesLock = new object();
|
||||
private readonly List<ProcessWrapper> _runningProcesses = new List<ProcessWrapper>();
|
||||
|
||||
private string _ffmpegPath;
|
||||
private string _ffmpegPath = string.Empty;
|
||||
private string _ffprobePath;
|
||||
|
||||
public MediaEncoder(
|
||||
|
@ -55,14 +56,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||
IFileSystem fileSystem,
|
||||
ILocalizationManager localization,
|
||||
Lazy<EncodingHelper> encodingHelperFactory,
|
||||
string startupOptionsFFmpegPath)
|
||||
IConfiguration config)
|
||||
{
|
||||
_logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_localization = localization;
|
||||
_encodingHelperFactory = encodingHelperFactory;
|
||||
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||
_startupOptionFFmpegPath = config.GetValue<string>(Controller.Extensions.ConfigurationExtensions.FfmpegPathKey) ?? string.Empty;
|
||||
}
|
||||
|
||||
private EncodingHelper EncodingHelper => _encodingHelperFactory.Value;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -82,8 +82,6 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public bool EnableRemoteAccess { get; set; }
|
||||
|
||||
public bool CollectionsUpgraded { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [enable case sensitive item ids].
|
||||
/// </summary>
|
||||
|
@ -269,6 +267,9 @@ namespace MediaBrowser.Model.Configuration
|
|||
PathSubstitutions = Array.Empty<PathSubstitution>();
|
||||
IgnoreVirtualInterfaces = false;
|
||||
EnableSimpleArtistDetection = false;
|
||||
SkipDeserializationForBasicTypes = true;
|
||||
|
||||
PluginRepositories = new List<RepositoryInfo>();
|
||||
|
||||
DisplaySpecialsWithinSeasons = true;
|
||||
EnableExternalContentInSuggestions = true;
|
||||
|
@ -282,6 +283,9 @@ namespace MediaBrowser.Model.Configuration
|
|||
EnableHttps = false;
|
||||
EnableDashboardResponseCaching = true;
|
||||
EnableCaseSensitiveItemIds = true;
|
||||
EnableNormalizedItemByNameIds = true;
|
||||
DisableLiveTvChannelUserDataName = true;
|
||||
EnableNewOmdbSupport = true;
|
||||
|
||||
AutoRunWebApp = true;
|
||||
EnableRemoteAccess = true;
|
||||
|
|
|
@ -206,7 +206,7 @@ namespace MediaBrowser.Model.Dlna
|
|||
}
|
||||
}
|
||||
|
||||
return new MediaFormatProfile[] { };
|
||||
return Array.Empty<MediaFormatProfile>();
|
||||
}
|
||||
|
||||
private MediaFormatProfile ValueOf(string value)
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,26 +1,36 @@
|
|||
#nullable disable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Model.Providers
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the external id information for serialization to the client.
|
||||
/// </summary>
|
||||
public class ExternalIdInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the name.
|
||||
/// Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc).
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; set; }
|
||||
// TODO: This should be renamed to ProviderName
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the key.
|
||||
/// Gets or sets the unique key for this id. This key should be unique across all providers.
|
||||
/// </summary>
|
||||
/// <value>The key.</value>
|
||||
public string Key { get; set; }
|
||||
// TODO: This property is not actually unique across the concrete types at the moment. It should be updated to be unique.
|
||||
public string? Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the specific media type for this id. This is used to distinguish between the different
|
||||
/// external id types for providers with multiple ids.
|
||||
/// A null value indicates there is no specific media type associated with the external id, or this is the
|
||||
/// default id for the external provider so there is no need to specify a type.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used along with the <see cref="Name"/> to localize the external id on the client.
|
||||
/// </remarks>
|
||||
public ExternalIdMediaType? Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL format string.
|
||||
/// </summary>
|
||||
/// <value>The URL format string.</value>
|
||||
public string UrlFormatString { get; set; }
|
||||
public string? UrlFormatString { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
namespace MediaBrowser.Model.Providers
|
||||
{
|
||||
/// <summary>
|
||||
/// The specific media type of an <see cref="ExternalIdInfo"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Client applications may use this as a translation key.
|
||||
/// </remarks>
|
||||
public enum ExternalIdMediaType
|
||||
{
|
||||
/// <summary>
|
||||
/// A music album.
|
||||
/// </summary>
|
||||
Album = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The artist of a music album.
|
||||
/// </summary>
|
||||
AlbumArtist = 2,
|
||||
|
||||
/// <summary>
|
||||
/// The artist of a media item.
|
||||
/// </summary>
|
||||
Artist = 3,
|
||||
|
||||
/// <summary>
|
||||
/// A boxed set of media.
|
||||
/// </summary>
|
||||
BoxSet = 4,
|
||||
|
||||
/// <summary>
|
||||
/// A series episode.
|
||||
/// </summary>
|
||||
Episode = 5,
|
||||
|
||||
/// <summary>
|
||||
/// A movie.
|
||||
/// </summary>
|
||||
Movie = 6,
|
||||
|
||||
/// <summary>
|
||||
/// An alternative artist apart from the main artist.
|
||||
/// </summary>
|
||||
OtherArtist = 7,
|
||||
|
||||
/// <summary>
|
||||
/// A person.
|
||||
/// </summary>
|
||||
Person = 8,
|
||||
|
||||
/// <summary>
|
||||
/// A release group.
|
||||
/// </summary>
|
||||
ReleaseGroup = 9,
|
||||
|
||||
/// <summary>
|
||||
/// A single season of a series.
|
||||
/// </summary>
|
||||
Season = 10,
|
||||
|
||||
/// <summary>
|
||||
/// A series.
|
||||
/// </summary>
|
||||
Series = 11,
|
||||
|
||||
/// <summary>
|
||||
/// A music track.
|
||||
/// </summary>
|
||||
Track = 12
|
||||
}
|
||||
}
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -102,7 +102,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
_metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
|
||||
_metadataProviders = metadataProviders.ToArray();
|
||||
_externalIds = externalIds.OrderBy(i => i.Name).ToArray();
|
||||
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
|
||||
|
||||
_savers = metadataSavers.Where(i =>
|
||||
{
|
||||
|
@ -900,7 +900,7 @@ namespace MediaBrowser.Providers.Manager
|
|||
|
||||
return new ExternalUrl
|
||||
{
|
||||
Name = i.Name,
|
||||
Name = i.ProviderName,
|
||||
Url = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
i.UrlFormatString,
|
||||
|
@ -914,8 +914,9 @@ namespace MediaBrowser.Providers.Manager
|
|||
return GetExternalIds(item)
|
||||
.Select(i => new ExternalIdInfo
|
||||
{
|
||||
Name = i.Name,
|
||||
Name = i.ProviderName,
|
||||
Key = i.Key,
|
||||
Type = i.Type,
|
||||
UrlFormatString = i.UrlFormatString
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,17 +6,21 @@ using MediaBrowser.Controller.Entities.TV;
|
|||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Movies
|
||||
{
|
||||
public class ImdbExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMDb";
|
||||
public string ProviderName => "IMDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Imdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.imdb.com/title/{0}";
|
||||
|
||||
|
@ -36,11 +40,14 @@ namespace MediaBrowser.Providers.Movies
|
|||
public class ImdbPersonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMDb";
|
||||
public string ProviderName => "IMDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Imdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.imdb.com/name/{0}";
|
||||
|
||||
|
|
|
@ -3,17 +3,21 @@
|
|||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
{
|
||||
public class ImvdbId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "IMVDb";
|
||||
public string ProviderName => "IMVDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => "IMVDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => null;
|
||||
|
||||
|
|
|
@ -3,17 +3,21 @@
|
|||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.AudioDb
|
||||
{
|
||||
public class AudioDbAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb";
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
|
@ -24,11 +28,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
|||
public class AudioDbOtherAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb Album";
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/album/{0}";
|
||||
|
||||
|
@ -39,11 +46,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
|||
public class AudioDbArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb";
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
|
@ -54,11 +64,14 @@ namespace MediaBrowser.Providers.Plugins.AudioDb
|
|||
public class AudioDbOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheAudioDb Artist";
|
||||
public string ProviderName => "TheAudioDb";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.AudioDbArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "https://www.theaudiodb.com/artist/{0}";
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Plugins.MusicBrainz;
|
||||
|
||||
namespace MediaBrowser.Providers.Music
|
||||
|
@ -10,11 +11,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzReleaseGroupExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Release Group";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzReleaseGroup.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.ReleaseGroup;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release-group/{0}";
|
||||
|
||||
|
@ -25,11 +29,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzAlbumArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album Artist";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzAlbumArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.AlbumArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
|
@ -40,11 +47,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzAlbumExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Album";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzAlbum.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Album;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/release/{0}";
|
||||
|
||||
|
@ -55,11 +65,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Artist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
|
@ -70,12 +83,15 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzOtherArtistExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Artist";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
public string Key => MetadataProvider.MusicBrainzArtist.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.OtherArtist;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/artist/{0}";
|
||||
|
||||
|
@ -86,11 +102,14 @@ namespace MediaBrowser.Providers.Music
|
|||
public class MusicBrainzTrackId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "MusicBrainz Track";
|
||||
public string ProviderName => "MusicBrainz";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.MusicBrainzTrack.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Track;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => Plugin.Instance.Configuration.Server + "/track/{0}";
|
||||
|
||||
|
|
|
@ -229,6 +229,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
|
||||
}
|
||||
|
||||
public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, 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, 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))
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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.TvdbImageBaseUrl + actor.Image;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(personInfo.Name))
|
||||
{
|
||||
result.AddPerson(personInfo);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.BoxSets
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB box set.
|
||||
/// </summary>
|
||||
public class TmdbBoxSetExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.TmdbCollection.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.BoxSet;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "collection/{0}";
|
||||
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMBD movie.
|
||||
/// </summary>
|
||||
public class TmdbMovieExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Movie;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "movie/{0}";
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.People
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB person.
|
||||
/// </summary>
|
||||
public class TmdbPersonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Person;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "person/{0}";
|
||||
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
|
||||
namespace MediaBrowser.Providers.Plugins.Tmdb.TV
|
||||
{
|
||||
/// <summary>
|
||||
/// External ID for a TMDB series.
|
||||
/// </summary>
|
||||
public class TmdbSeriesExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => TmdbUtils.ProviderName;
|
||||
public string ProviderName => TmdbUtils.ProviderName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tmdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Series;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TmdbUtils.BaseTmdbUrl + "tv/{0}";
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Providers;
|
||||
using MediaBrowser.Providers.Plugins.TheTvdb;
|
||||
|
||||
namespace MediaBrowser.Providers.TV
|
||||
|
@ -10,11 +11,14 @@ namespace MediaBrowser.Providers.TV
|
|||
public class Zap2ItExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "Zap2It";
|
||||
public string ProviderName => "Zap2It";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Zap2It.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => "http://tvlistings.zap2it.com/overview.html?programSeriesId={0}";
|
||||
|
||||
|
@ -25,11 +29,14 @@ namespace MediaBrowser.Providers.TV
|
|||
public class TvdbExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheTVDB";
|
||||
public string ProviderName => "TheTVDB";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tvdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=series&id={0}";
|
||||
|
||||
|
@ -40,11 +47,14 @@ namespace MediaBrowser.Providers.TV
|
|||
public class TvdbSeasonExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheTVDB";
|
||||
public string ProviderName => "TheTVDB";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tvdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Season;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => null;
|
||||
|
||||
|
@ -55,11 +65,14 @@ namespace MediaBrowser.Providers.TV
|
|||
public class TvdbEpisodeExternalId : IExternalId
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name => "TheTVDB";
|
||||
public string ProviderName => "TheTVDB";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Key => MetadataProvider.Tvdb.ToString();
|
||||
|
||||
/// <inheritdoc />
|
||||
public ExternalIdMediaType? Type => ExternalIdMediaType.Episode;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string UrlFormatString => TvdbUtils.TvdbBaseUrl + "?tab=episode&id={0}";
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
<a href="https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/?utm_source=widget">
|
||||
<img alt="Translation Status" src="https://translate.jellyfin.org/widgets/jellyfin/-/jellyfin-core/svg-badge.svg"/>
|
||||
</a>
|
||||
<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=1">
|
||||
<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20CI"/>
|
||||
<a href="https://dev.azure.com/jellyfin-project/jellyfin/_build?definitionId=29">
|
||||
<img alt="Azure Builds" src="https://dev.azure.com/jellyfin-project/jellyfin/_apis/build/status/Jellyfin%20Server"/>
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/jellyfin/jellyfin">
|
||||
<img alt="Docker Pull Count" src="https://img.shields.io/docker/pulls/jellyfin/jellyfin.svg"/>
|
||||
|
|
|
@ -339,7 +339,6 @@ namespace Rssdp.Infrastructure
|
|||
private ISocket ListenForBroadcastsAsync()
|
||||
{
|
||||
var socket = _SocketFactory.CreateUdpMulticastSocket(SsdpConstants.MulticastLocalAdminAddress, _MulticastTtl, SsdpConstants.MulticastPort);
|
||||
|
||||
_ = ListenToSocketInternal(socket);
|
||||
|
||||
return socket;
|
||||
|
|
|
@ -8,7 +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_VERSION="ffmpeg-4.3-win64-static"
|
||||
FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
|
||||
|
||||
# Move to source directory
|
||||
|
|
|
@ -13,15 +13,15 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.11.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.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.3" />
|
||||
<PackageReference Include="Moq" Version="4.14.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Code Analyzers -->
|
||||
|
|
|
@ -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) };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoFixture" Version="4.11.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.14.3" />
|
||||
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
|
||||
<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" />
|
||||
|
|
|
@ -7,12 +7,19 @@ namespace Jellyfin.Server.Implementations.Tests.Library
|
|||
{
|
||||
[Theory]
|
||||
[InlineData("/media/small.jpg", true)]
|
||||
[InlineData("/media/albumart.jpg", true)]
|
||||
[InlineData("/media/movie.sample.mp4", true)]
|
||||
[InlineData("/media/movies/#Recycle/test.txt", true)]
|
||||
[InlineData("/media/movies/#recycle/", true)]
|
||||
[InlineData("/media/movies/#recycle", true)]
|
||||
[InlineData("thumbs.db", true)]
|
||||
[InlineData(@"C:\media\movies\movie.avi", false)]
|
||||
[InlineData("/media/.hiddendir/file.mp4", true)]
|
||||
[InlineData("/media/dir/.hiddenfile.mp4", true)]
|
||||
[InlineData("/volume1/video/Series/@eaDir", true)]
|
||||
[InlineData("/volume1/video/Series/@eaDir/file.txt", true)]
|
||||
[InlineData("/directory/@Recycle", true)]
|
||||
[InlineData("/directory/@Recycle/file.mp3", true)]
|
||||
public void PathIgnored(string path, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IgnorePatterns.ShouldIgnore(path));
|
||||
|
|
Loading…
Reference in New Issue