Merge branch 'master' into NetworkPR2

This commit is contained in:
BaronGreenback 2020-10-26 13:21:48 +00:00 committed by GitHub
commit 0b5ddc90ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
164 changed files with 984 additions and 793 deletions

View File

@ -28,7 +28,13 @@ jobs:
inputs: inputs:
script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar" script: "wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${{ parameters.GeneratorVersion }}/openapi-generator-cli-${{ parameters.GeneratorVersion }}.jar -O openapi-generator-cli.jar"
# Generate npm api client ## Authenticate with npm registry
- task: npmAuthenticate@0
inputs:
workingFile: ./.npmrc
customEndpoint: 'jellyfin-bot for NPM'
## Generate npm api client
# Unstable # Unstable
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Build unstable typescript axios client' displayName: 'Build unstable typescript axios client'
@ -36,15 +42,6 @@ jobs:
inputs: inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory) $(Build.BuildNumber)" script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory) $(Build.BuildNumber)"
- task: Npm@1
displayName: 'Publish unstable typescript axios client'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
command: publish
publishRegistry: useFeed
publishFeed: 'unstable@Local'
workingDir: ./apiclient/generated/typescript/axios
# Stable # Stable
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Build stable typescript axios client' displayName: 'Build stable typescript axios client'
@ -52,6 +49,25 @@ jobs:
inputs: inputs:
script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)" script: "bash ./apiclient/templates/typescript/axios/generate.sh $(System.ArtifactsDirectory)"
## Run npm install
- task: Npm@1
displayName: 'Install npm dependencies'
inputs:
command: install
workingDir: ./apiclient/generated/typescript/axios
## Publish npm packages
# Unstable
- task: Npm@1
displayName: 'Publish unstable typescript axios client'
condition: startsWith(variables['Build.SourceBranch'], 'refs/heads/master')
inputs:
command: publish
publishRegistry: useFeed
publishFeed: 'jellyfin/unstable'
workingDir: ./apiclient/generated/typescript/axios
# Stable
- task: Npm@1 - task: Npm@1
displayName: 'Publish stable typescript axios client' displayName: 'Publish stable typescript axios client'
condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v')

3
.npmrc Normal file
View File

@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
@jellyfin:registry=https://pkgs.dev.azure.com/jellyfin-project/jellyfin/_packaging/unstable/npm/registry/
always-auth=true

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml; using System.Xml;
@ -10,8 +8,16 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar namespace Emby.Dlna.MediaReceiverRegistrar
{ {
/// <summary>
/// Defines the <see cref="ControlHandler" />.
/// </summary>
public class ControlHandler : BaseControlHandler public class ControlHandler : BaseControlHandler
{ {
/// <summary>
/// Initializes a new instance of the <see cref="ControlHandler"/> class.
/// </summary>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
/// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param>
public ControlHandler(IServerConfigurationManager config, ILogger logger) public ControlHandler(IServerConfigurationManager config, ILogger logger)
: base(config, logger) : base(config, logger)
{ {
@ -35,9 +41,17 @@ namespace Emby.Dlna.MediaReceiverRegistrar
throw new ResourceNotFoundException("Unexpected control request name: " + methodName); throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
} }
/// <summary>
/// Records that the handle is authorized in the xml stream.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private static void HandleIsAuthorized(XmlWriter xmlWriter) private static void HandleIsAuthorized(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1"); => xmlWriter.WriteElementString("Result", "1");
/// <summary>
/// Records that the handle is validated in the xml stream.
/// </summary>
/// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
private static void HandleIsValidated(XmlWriter xmlWriter) private static void HandleIsValidated(XmlWriter xmlWriter)
=> xmlWriter.WriteElementString("Result", "1"); => xmlWriter.WriteElementString("Result", "1");
} }

View File

@ -1,5 +1,3 @@
#pragma warning disable CS1591
using System.Net.Http; using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
using Emby.Dlna.Service; using Emby.Dlna.Service;
@ -8,10 +6,19 @@ using Microsoft.Extensions.Logging;
namespace Emby.Dlna.MediaReceiverRegistrar namespace Emby.Dlna.MediaReceiverRegistrar
{ {
/// <summary>
/// Defines the <see cref="MediaReceiverRegistrarService" />.
/// </summary>
public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar public class MediaReceiverRegistrarService : BaseService, IMediaReceiverRegistrar
{ {
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="MediaReceiverRegistrarService"/> class.
/// </summary>
/// <param name="logger">The <see cref="ILogger{MediaReceiverRegistrarService}"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
/// <param name="httpClientFactory">The <see cref="IHttpClientFactory"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
/// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="MediaReceiverRegistrarService"/> instance.</param>
public MediaReceiverRegistrarService( public MediaReceiverRegistrarService(
ILogger<MediaReceiverRegistrarService> logger, ILogger<MediaReceiverRegistrarService> logger,
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
@ -24,7 +31,7 @@ namespace Emby.Dlna.MediaReceiverRegistrar
/// <inheritdoc /> /// <inheritdoc />
public string GetServiceXml() public string GetServiceXml()
{ {
return new MediaReceiverRegistrarXmlBuilder().GetXml(); return MediaReceiverRegistrarXmlBuilder.GetXml();
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -1,79 +1,89 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using Emby.Dlna.Service; using Emby.Dlna.Service;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar namespace Emby.Dlna.MediaReceiverRegistrar
{ {
public class MediaReceiverRegistrarXmlBuilder /// <summary>
/// Defines the <see cref="MediaReceiverRegistrarXmlBuilder" />.
/// See https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-drmnd/5d37515e-7a63-4709-8258-8fd4e0ed4482.
/// </summary>
public static class MediaReceiverRegistrarXmlBuilder
{ {
public string GetXml() /// <summary>
/// Retrieves an XML description of the X_MS_MediaReceiverRegistrar.
/// </summary>
/// <returns>An XML representation of this service.</returns>
public static string GetXml()
{ {
return new ServiceXmlBuilder().GetXml( return new ServiceXmlBuilder().GetXml(ServiceActionListBuilder.GetActions(), GetStateVariables());
new ServiceActionListBuilder().GetActions(),
GetStateVariables());
} }
/// <summary>
/// The a list of all the state variables for this invocation.
/// </summary>
/// <returns>The <see cref="IEnumerable{StateVariable}"/>.</returns>
private static IEnumerable<StateVariable> GetStateVariables() private static IEnumerable<StateVariable> GetStateVariables()
{ {
var list = new List<StateVariable>(); var list = new List<StateVariable>
list.Add(new StateVariable
{ {
Name = "AuthorizationGrantedUpdateID", new StateVariable
DataType = "ui4", {
SendsEvents = true Name = "AuthorizationGrantedUpdateID",
}); DataType = "ui4",
SendsEvents = true
},
list.Add(new StateVariable new StateVariable
{ {
Name = "A_ARG_TYPE_DeviceID", Name = "A_ARG_TYPE_DeviceID",
DataType = "string", DataType = "string",
SendsEvents = false SendsEvents = false
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "AuthorizationDeniedUpdateID", Name = "AuthorizationDeniedUpdateID",
DataType = "ui4", DataType = "ui4",
SendsEvents = true SendsEvents = true
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "ValidationSucceededUpdateID", Name = "ValidationSucceededUpdateID",
DataType = "ui4", DataType = "ui4",
SendsEvents = true SendsEvents = true
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "A_ARG_TYPE_RegistrationRespMsg", Name = "A_ARG_TYPE_RegistrationRespMsg",
DataType = "bin.base64", DataType = "bin.base64",
SendsEvents = false SendsEvents = false
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "A_ARG_TYPE_RegistrationReqMsg", Name = "A_ARG_TYPE_RegistrationReqMsg",
DataType = "bin.base64", DataType = "bin.base64",
SendsEvents = false SendsEvents = false
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "ValidationRevokedUpdateID", Name = "ValidationRevokedUpdateID",
DataType = "ui4", DataType = "ui4",
SendsEvents = true SendsEvents = true
}); },
list.Add(new StateVariable new StateVariable
{ {
Name = "A_ARG_TYPE_Result", Name = "A_ARG_TYPE_Result",
DataType = "int", DataType = "int",
SendsEvents = false SendsEvents = false
}); }
};
return list; return list;
} }

View File

@ -1,13 +1,19 @@
#pragma warning disable CS1591
using System.Collections.Generic; using System.Collections.Generic;
using Emby.Dlna.Common; using Emby.Dlna.Common;
using MediaBrowser.Model.Dlna;
namespace Emby.Dlna.MediaReceiverRegistrar namespace Emby.Dlna.MediaReceiverRegistrar
{ {
public class ServiceActionListBuilder /// <summary>
/// Defines the <see cref="ServiceActionListBuilder" />.
/// </summary>
public static class ServiceActionListBuilder
{ {
public IEnumerable<ServiceAction> GetActions() /// <summary>
/// Returns a list of services that this instance provides.
/// </summary>
/// <returns>An <see cref="IEnumerable{ServiceAction}"/>.</returns>
public static IEnumerable<ServiceAction> GetActions()
{ {
return new[] return new[]
{ {
@ -21,6 +27,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar
}; };
} }
/// <summary>
/// Returns the action details for "IsValidated".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetIsValidated() private static ServiceAction GetIsValidated()
{ {
var action = new ServiceAction var action = new ServiceAction
@ -43,6 +53,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
/// <summary>
/// Returns the action details for "IsAuthorized".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetIsAuthorized() private static ServiceAction GetIsAuthorized()
{ {
var action = new ServiceAction var action = new ServiceAction
@ -65,6 +79,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
/// <summary>
/// Returns the action details for "RegisterDevice".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetRegisterDevice() private static ServiceAction GetRegisterDevice()
{ {
var action = new ServiceAction var action = new ServiceAction
@ -87,6 +105,10 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
/// <summary>
/// Returns the action details for "GetValidationSucceededUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetValidationSucceededUpdateID() private static ServiceAction GetGetValidationSucceededUpdateID()
{ {
var action = new ServiceAction var action = new ServiceAction
@ -103,7 +125,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
private ServiceAction GetGetAuthorizationDeniedUpdateID() /// <summary>
/// Returns the action details for "GetGetAuthorizationDeniedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetAuthorizationDeniedUpdateID()
{ {
var action = new ServiceAction var action = new ServiceAction
{ {
@ -119,7 +145,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
private ServiceAction GetGetValidationRevokedUpdateID() /// <summary>
/// Returns the action details for "GetValidationRevokedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetValidationRevokedUpdateID()
{ {
var action = new ServiceAction var action = new ServiceAction
{ {
@ -135,7 +165,11 @@ namespace Emby.Dlna.MediaReceiverRegistrar
return action; return action;
} }
private ServiceAction GetGetAuthorizationGrantedUpdateID() /// <summary>
/// Returns the action details for "GetAuthorizationGrantedUpdateID".
/// </summary>
/// <returns>The <see cref="ServiceAction"/>.</returns>
private static ServiceAction GetGetAuthorizationGrantedUpdateID()
{ {
var action = new ServiceAction var action = new ServiceAction
{ {

View File

@ -235,13 +235,13 @@ namespace Emby.Dlna.Server
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty)) .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
.Append("</serviceId>"); .Append("</serviceId>");
builder.Append("<SCPDURL>") builder.Append("<SCPDURL>")
.Append(BuildUrl(service.ScpdUrl, true)) .Append(BuildUrl(service.ScpdUrl))
.Append("</SCPDURL>"); .Append("</SCPDURL>");
builder.Append("<controlURL>") builder.Append("<controlURL>")
.Append(BuildUrl(service.ControlUrl, true)) .Append(BuildUrl(service.ControlUrl))
.Append("</controlURL>"); .Append("</controlURL>");
builder.Append("<eventSubURL>") builder.Append("<eventSubURL>")
.Append(BuildUrl(service.EventSubUrl, true)) .Append(BuildUrl(service.EventSubUrl))
.Append("</eventSubURL>"); .Append("</eventSubURL>");
builder.Append("</service>"); builder.Append("</service>");
@ -250,13 +250,7 @@ namespace Emby.Dlna.Server
builder.Append("</serviceList>"); builder.Append("</serviceList>");
} }
/// <summary> private string BuildUrl(string url)
/// Builds a valid url for inclusion in the xml.
/// </summary>
/// <param name="url">Url to include.</param>
/// <param name="absoluteUrl">Optional. When set to true, the absolute url is always used.</param>
/// <returns>The url to use for the element.</returns>
private string BuildUrl(string url, bool absoluteUrl = false)
{ {
if (string.IsNullOrEmpty(url)) if (string.IsNullOrEmpty(url))
{ {
@ -267,7 +261,7 @@ namespace Emby.Dlna.Server
url = "/dlna/" + _serverUdn + "/" + url; url = "/dlna/" + _serverUdn + "/" + url;
if (EnableAbsoluteUrls || absoluteUrl) if (EnableAbsoluteUrls)
{ {
url = _serverAddress.TrimEnd('/') + url; url = _serverAddress.TrimEnd('/') + url;
} }

View File

@ -60,10 +60,8 @@ namespace Emby.Dlna.Service
Async = true Async = true
}; };
using (var reader = XmlReader.Create(streamReader, readerSettings)) using var reader = XmlReader.Create(streamReader, readerSettings);
{ requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
requestInfo = await ParseRequestAsync(reader).ConfigureAwait(false);
}
} }
Logger.LogDebug("Received control request {0}", requestInfo.LocalName); Logger.LogDebug("Received control request {0}", requestInfo.LocalName);
@ -124,10 +122,8 @@ namespace Emby.Dlna.Service
{ {
if (!reader.IsEmptyElement) if (!reader.IsEmptyElement)
{ {
using (var subReader = reader.ReadSubtree()) using var subReader = reader.ReadSubtree();
{ return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
return await ParseBodyTagAsync(subReader).ConfigureAwait(false);
}
} }
else else
{ {
@ -150,12 +146,12 @@ namespace Emby.Dlna.Service
} }
} }
return new ControlRequestInfo(); throw new EndOfStreamException("Stream ended but no body tag found.");
} }
private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader) private async Task<ControlRequestInfo> ParseBodyTagAsync(XmlReader reader)
{ {
var result = new ControlRequestInfo(); string namespaceURI = null, localName = null;
await reader.MoveToContentAsync().ConfigureAwait(false); await reader.MoveToContentAsync().ConfigureAwait(false);
await reader.ReadAsync().ConfigureAwait(false); await reader.ReadAsync().ConfigureAwait(false);
@ -165,16 +161,14 @@ namespace Emby.Dlna.Service
{ {
if (reader.NodeType == XmlNodeType.Element) if (reader.NodeType == XmlNodeType.Element)
{ {
result.LocalName = reader.LocalName; localName = reader.LocalName;
result.NamespaceURI = reader.NamespaceURI; namespaceURI = reader.NamespaceURI;
if (!reader.IsEmptyElement) if (!reader.IsEmptyElement)
{ {
using (var subReader = reader.ReadSubtree()) var result = new ControlRequestInfo(localName, namespaceURI);
{ using var subReader = reader.ReadSubtree();
await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false); await ParseFirstBodyChildAsync(subReader, result.Headers).ConfigureAwait(false);
return result;
}
} }
else else
{ {
@ -187,7 +181,12 @@ namespace Emby.Dlna.Service
} }
} }
return result; if (localName != null && namespaceURI != null)
{
return new ControlRequestInfo(localName, namespaceURI);
}
throw new EndOfStreamException("Stream ended but no control found.");
} }
private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers) private async Task ParseFirstBodyChildAsync(XmlReader reader, IDictionary<string, string> headers)
@ -234,11 +233,18 @@ namespace Emby.Dlna.Service
private class ControlRequestInfo private class ControlRequestInfo
{ {
public ControlRequestInfo(string localName, string namespaceUri)
{
LocalName = localName;
NamespaceURI = namespaceUri;
Headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public string LocalName { get; set; } public string LocalName { get; set; }
public string NamespaceURI { get; set; } public string NamespaceURI { get; set; }
public Dictionary<string, string> Headers { get; } = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, string> Headers { get; }
} }
} }
} }

View File

@ -36,7 +36,7 @@ namespace Emby.Drawing
private readonly IImageEncoder _imageEncoder; private readonly IImageEncoder _imageEncoder;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private bool _disposed = false; private bool _disposed;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ImageProcessor"/> class. /// Initializes a new instance of the <see cref="ImageProcessor"/> class.
@ -466,11 +466,11 @@ namespace Emby.Drawing
} }
/// <inheritdoc /> /// <inheritdoc />
public void CreateImageCollage(ImageCollageOptions options) public void CreateImageCollage(ImageCollageOptions options, string? libraryName)
{ {
_logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath); _logger.LogInformation("Creating image collage and saving to {Path}", options.OutputPath);
_imageEncoder.CreateImageCollage(options); _imageEncoder.CreateImageCollage(options, libraryName);
_logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath); _logger.LogInformation("Completed creation of image collage and saved to {Path}", options.OutputPath);
} }

View File

@ -38,7 +38,7 @@ namespace Emby.Drawing
} }
/// <inheritdoc /> /// <inheritdoc />
public void CreateImageCollage(ImageCollageOptions options) public void CreateImageCollage(ImageCollageOptions options, string? libraryName)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -1,3 +1,4 @@
#nullable enable
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
@ -16,21 +17,11 @@ namespace Emby.Naming.AudioBook
_options = options; _options = options;
} }
public AudioBookFileInfo ParseFile(string path) public AudioBookFileInfo? Resolve(string path, bool isDirectory = false)
{ {
return Resolve(path, false); if (path.Length == 0)
}
public AudioBookFileInfo ParseDirectory(string path)
{
return Resolve(path, true);
}
public AudioBookFileInfo Resolve(string path, bool isDirectory = false)
{
if (string.IsNullOrEmpty(path))
{ {
throw new ArgumentNullException(nameof(path)); throw new ArgumentException("String can't be empty.", nameof(path));
} }
// TODO // TODO

View File

@ -133,7 +133,6 @@ namespace Emby.Server.Implementations
private IMediaEncoder _mediaEncoder; private IMediaEncoder _mediaEncoder;
private ISessionManager _sessionManager; private ISessionManager _sessionManager;
private IHttpClientFactory _httpClientFactory; private IHttpClientFactory _httpClientFactory;
private IWebSocketManager _webSocketManager;
private string[] _urlPrefixes; private string[] _urlPrefixes;
@ -678,7 +677,6 @@ namespace Emby.Server.Implementations
_mediaEncoder = Resolve<IMediaEncoder>(); _mediaEncoder = Resolve<IMediaEncoder>();
_sessionManager = Resolve<ISessionManager>(); _sessionManager = Resolve<ISessionManager>();
_httpClientFactory = Resolve<IHttpClientFactory>(); _httpClientFactory = Resolve<IHttpClientFactory>();
_webSocketManager = Resolve<IWebSocketManager>();
((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize(); ((AuthenticationRepository)Resolve<IAuthenticationRepository>()).Initialize();
@ -799,7 +797,6 @@ namespace Emby.Server.Implementations
.ToArray(); .ToArray();
_urlPrefixes = GetUrlPrefixes().ToArray(); _urlPrefixes = GetUrlPrefixes().ToArray();
_webSocketManager.Init(GetExports<IWebSocketListener>());
Resolve<ILibraryManager>().AddParts( Resolve<ILibraryManager>().AddParts(
GetExports<IResolverIgnoreRule>(), GetExports<IResolverIgnoreRule>(),
@ -832,38 +829,6 @@ namespace Emby.Server.Implementations
{ {
try try
{ {
if (plugin is IPluginAssembly assemblyPlugin)
{
var assembly = plugin.GetType().Assembly;
var assemblyName = assembly.GetName();
var assemblyFilePath = assembly.Location;
var dataFolderPath = Path.Combine(ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(assemblyFilePath));
assemblyPlugin.SetAttributes(assemblyFilePath, dataFolderPath, assemblyName.Version);
try
{
var idAttributes = assembly.GetCustomAttributes(typeof(GuidAttribute), true);
if (idAttributes.Length > 0)
{
var attribute = (GuidAttribute)idAttributes[0];
var assemblyId = new Guid(attribute.Value);
assemblyPlugin.SetId(assemblyId);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error getting plugin Id from {PluginName}.", plugin.GetType().FullName);
}
}
if (plugin is IHasPluginConfiguration hasPluginConfiguration)
{
hasPluginConfiguration.SetStartupInfo(s => Directory.CreateDirectory(s));
}
plugin.RegisterServices(ServiceCollection); plugin.RegisterServices(ServiceCollection);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -157,7 +157,8 @@ namespace Emby.Server.Implementations.Data
protected bool TableExists(ManagedConnection connection, string name) protected bool TableExists(ManagedConnection connection, string name)
{ {
return connection.RunInTransaction(db => return connection.RunInTransaction(
db =>
{ {
using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master")) using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
{ {

View File

@ -219,7 +219,8 @@ namespace Emby.Server.Implementations.Data
{ {
connection.RunQueries(queries); connection.RunQueries(queries);
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var existingColumnNames = GetColumnNames(db, "AncestorIds"); var existingColumnNames = GetColumnNames(db, "AncestorIds");
AddColumn(db, "AncestorIds", "AncestorIdText", "Text", existingColumnNames); AddColumn(db, "AncestorIds", "AncestorIdText", "Text", existingColumnNames);
@ -495,7 +496,8 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id")) using (var saveImagesStatement = base.PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
{ {
@ -546,7 +548,8 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
SaveItemsInTranscation(db, tuples); SaveItemsInTranscation(db, tuples);
}, TransactionMode); }, TransactionMode);
@ -2032,7 +2035,8 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
// First delete chapters // First delete chapters
db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob); db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
@ -2921,7 +2925,8 @@ namespace Emby.Server.Implementations.Data
var result = new QueryResult<BaseItem>(); var result = new QueryResult<BaseItem>();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var statements = PrepareAll(db, statementTexts); var statements = PrepareAll(db, statementTexts);
@ -3324,7 +3329,8 @@ namespace Emby.Server.Implementations.Data
var result = new QueryResult<Guid>(); var result = new QueryResult<Guid>();
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var statements = PrepareAll(db, statementTexts); var statements = PrepareAll(db, statementTexts);
@ -4899,7 +4905,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
connection.ExecuteAll(sql); connection.ExecuteAll(sql);
}, TransactionMode); }, TransactionMode);
@ -4950,7 +4957,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var idBlob = id.ToByteArray(); var idBlob = id.ToByteArray();
@ -5357,7 +5365,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
itemCountColumns = new Dictionary<string, string>() itemCountColumns = new Dictionary<string, string>()
{ {
{ "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes"} { "itemTypes", "(" + itemCountColumnQuery + ") as itemTypes" }
}; };
} }
@ -5744,7 +5752,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var itemIdBlob = itemId.ToByteArray(); var itemIdBlob = itemId.ToByteArray();
@ -5898,7 +5907,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var itemIdBlob = id.ToByteArray(); var itemIdBlob = id.ToByteArray();
@ -6232,7 +6242,8 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var itemIdBlob = id.ToByteArray(); var itemIdBlob = id.ToByteArray();

View File

@ -44,7 +44,8 @@ namespace Emby.Server.Implementations.Data
var users = userDatasTableExists ? null : userManager.Users; var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
db.ExecuteAll(string.Join(";", new[] { db.ExecuteAll(string.Join(";", new[] {
@ -178,7 +179,8 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
SaveUserData(db, internalUserId, key, userData); SaveUserData(db, internalUserId, key, userData);
}, TransactionMode); }, TransactionMode);
@ -246,7 +248,8 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
foreach (var userItemData in userDataList) foreach (var userItemData in userDataList)
{ {

View File

@ -31,10 +31,10 @@
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" /> <PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" /> <PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.2.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.9" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.8" /> <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.9" />
<PackageReference Include="Mono.Nat" Version="3.0.0" /> <PackageReference Include="Mono.Nat" Version="3.0.0" />
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" /> <PackageReference Include="prometheus-net.DotNetRuntime" Version="3.4.0" />
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.9.2" />

View File

@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Events; using Jellyfin.Data.Events;
@ -14,16 +13,18 @@ namespace Emby.Server.Implementations.HttpServer
{ {
public class WebSocketManager : IWebSocketManager public class WebSocketManager : IWebSocketManager
{ {
private readonly Lazy<IEnumerable<IWebSocketListener>> _webSocketListeners;
private readonly ILogger<WebSocketManager> _logger; private readonly ILogger<WebSocketManager> _logger;
private readonly ILoggerFactory _loggerFactory; private readonly ILoggerFactory _loggerFactory;
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
private bool _disposed = false; private bool _disposed = false;
public WebSocketManager( public WebSocketManager(
Lazy<IEnumerable<IWebSocketListener>> webSocketListeners,
ILogger<WebSocketManager> logger, ILogger<WebSocketManager> logger,
ILoggerFactory loggerFactory) ILoggerFactory loggerFactory)
{ {
_webSocketListeners = webSocketListeners;
_logger = logger; _logger = logger;
_loggerFactory = loggerFactory; _loggerFactory = loggerFactory;
} }
@ -68,15 +69,6 @@ namespace Emby.Server.Implementations.HttpServer
} }
} }
/// <summary>
/// Adds the rest handlers.
/// </summary>
/// <param name="listeners">The web socket listeners.</param>
public void Init(IEnumerable<IWebSocketListener> listeners)
{
_webSocketListeners = listeners.ToArray();
}
/// <summary> /// <summary>
/// Processes the web socket message received. /// Processes the web socket message received.
/// </summary> /// </summary>
@ -90,7 +82,8 @@ namespace Emby.Server.Implementations.HttpServer
IEnumerable<Task> GetTasks() IEnumerable<Task> GetTasks()
{ {
foreach (var x in _webSocketListeners) var listeners = _webSocketListeners.Value;
foreach (var x in listeners)
{ {
yield return x.ProcessMessageAsync(result); yield return x.ProcessMessageAsync(result);
} }

View File

@ -133,9 +133,20 @@ namespace Emby.Server.Implementations.Images
protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items) protected virtual IEnumerable<string> GetStripCollageImagePaths(BaseItem primaryItem, IEnumerable<BaseItem> items)
{ {
var useBackdrop = primaryItem is CollectionFolder;
return items return items
.Select(i => .Select(i =>
{ {
// Use Backdrop instead of Primary image for Library images.
if (useBackdrop)
{
var backdrop = i.GetImageInfo(ImageType.Backdrop, 0);
if (backdrop != null && backdrop.IsLocalFile)
{
return backdrop.Path;
}
}
var image = i.GetImageInfo(ImageType.Primary, 0); var image = i.GetImageInfo(ImageType.Primary, 0);
if (image != null && image.IsLocalFile) if (image != null && image.IsLocalFile)
{ {
@ -190,7 +201,7 @@ namespace Emby.Server.Implementations.Images
return null; return null;
} }
ImageProcessor.CreateImageCollage(options); ImageProcessor.CreateImageCollage(options, primaryItem.Name);
return outputPath; return outputPath;
} }

View File

@ -1,6 +1,7 @@
#pragma warning disable CS1591 #pragma warning disable CS1591
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -43,7 +44,7 @@ namespace Emby.Server.Implementations.Library
private readonly ILocalizationManager _localizationManager; private readonly ILocalizationManager _localizationManager;
private readonly IApplicationPaths _appPaths; private readonly IApplicationPaths _appPaths;
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase); private readonly ConcurrentDictionary<string, ILiveStream> _openStreams = new ConcurrentDictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1); private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
private IMediaSourceProvider[] _providers; private IMediaSourceProvider[] _providers;
@ -582,29 +583,20 @@ namespace Emby.Server.Implementations.Library
mediaSource.InferTotalBitrate(); mediaSource.InferTotalBitrate();
} }
public async Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken) public Task<IDirectStreamProvider> GetDirectStreamProviderByUniqueId(string uniqueId, CancellationToken cancellationToken)
{ {
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); var info = _openStreams.Values.FirstOrDefault(i =>
try
{ {
var info = _openStreams.Values.FirstOrDefault(i => var liveStream = i as ILiveStream;
if (liveStream != null)
{ {
var liveStream = i as ILiveStream; return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
if (liveStream != null) }
{
return string.Equals(liveStream.UniqueId, uniqueId, StringComparison.OrdinalIgnoreCase);
}
return false; return false;
}); });
return info as IDirectStreamProvider; return Task.FromResult(info as IDirectStreamProvider);
}
finally
{
_liveStreamSemaphore.Release();
}
} }
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken) public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, CancellationToken cancellationToken)
@ -793,29 +785,20 @@ namespace Emby.Server.Implementations.Library
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider); return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info as IDirectStreamProvider);
} }
private async Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken) private Task<ILiveStream> GetLiveStreamInfo(string id, CancellationToken cancellationToken)
{ {
if (string.IsNullOrEmpty(id)) if (string.IsNullOrEmpty(id))
{ {
throw new ArgumentNullException(nameof(id)); throw new ArgumentNullException(nameof(id));
} }
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); if (_openStreams.TryGetValue(id, out ILiveStream info))
try
{ {
if (_openStreams.TryGetValue(id, out ILiveStream info)) return Task.FromResult(info);
{
return info;
}
else
{
throw new ResourceNotFoundException();
}
} }
finally else
{ {
_liveStreamSemaphore.Release(); return Task.FromException<ILiveStream>(new ResourceNotFoundException());
} }
} }
@ -844,7 +827,7 @@ namespace Emby.Server.Implementations.Library
if (liveStream.ConsumerCount <= 0) if (liveStream.ConsumerCount <= 0)
{ {
_openStreams.Remove(id); _openStreams.TryRemove(id, out _);
_logger.LogInformation("Closing live stream {0}", id); _logger.LogInformation("Closing live stream {0}", id);

View File

@ -32,7 +32,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// <value>The priority.</value> /// <value>The priority.</value>
public override ResolverPriority Priority => ResolverPriority.Fourth; public override ResolverPriority Priority => ResolverPriority.Fourth;
public MultiItemResolverResult ResolveMultiple(Folder parent, public MultiItemResolverResult ResolveMultiple(
Folder parent,
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
string collectionType, string collectionType,
IDirectoryService directoryService) IDirectoryService directoryService)
@ -50,7 +51,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
return result; return result;
} }
private MultiItemResolverResult ResolveMultipleInternal(Folder parent, private MultiItemResolverResult ResolveMultipleInternal(
Folder parent,
List<FileSystemMetadata> files, List<FileSystemMetadata> files,
string collectionType, string collectionType,
IDirectoryService directoryService) IDirectoryService directoryService)

View File

@ -1,5 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Audio; using Emby.Naming.Audio;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -113,52 +116,48 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
IFileSystem fileSystem, IFileSystem fileSystem,
ILibraryManager libraryManager) ILibraryManager libraryManager)
{ {
// check for audio files before digging down into directories
var foundAudioFile = list.Any(fileSystemInfo => !fileSystemInfo.IsDirectory && libraryManager.IsAudioFile(fileSystemInfo.FullName));
if (foundAudioFile)
{
// at least one audio file exists
return true;
}
if (!allowSubfolders)
{
// not music since no audio file exists and we're not looking into subfolders
return false;
}
var discSubfolderCount = 0; var discSubfolderCount = 0;
var notMultiDisc = false;
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(); var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var parser = new AlbumParser(namingOptions); var parser = new AlbumParser(namingOptions);
foreach (var fileSystemInfo in list)
var directories = list.Where(fileSystemInfo => fileSystemInfo.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{ {
if (fileSystemInfo.IsDirectory) var path = fileSystemInfo.FullName;
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager);
if (hasMusic)
{ {
if (allowSubfolders) if (parser.IsMultiPart(path))
{ {
if (notMultiDisc) logger.LogDebug("Found multi-disc folder: " + path);
{ Interlocked.Increment(ref discSubfolderCount);
continue; }
} else
{
var path = fileSystemInfo.FullName; // If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryManager); state.Stop();
if (hasMusic)
{
if (parser.IsMultiPart(path))
{
logger.LogDebug("Found multi-disc folder: " + path);
discSubfolderCount++;
}
else
{
// If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
notMultiDisc = true;
}
}
} }
} }
else });
{
var fullName = fileSystemInfo.FullName;
if (libraryManager.IsAudioFile(fullName)) if (!result.IsCompleted)
{
return true;
}
}
}
if (notMultiDisc)
{ {
return false; return false;
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
@ -94,7 +95,18 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager); var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
// If we contain an album assume we are an artist folder // If we contain an album assume we are an artist folder
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService)) ? new MusicArtist() : null; var directories = args.FileSystemChildren.Where(i => i.IsDirectory);
var result = Parallel.ForEach(directories, (fileSystemInfo, state) =>
{
if (albumResolver.IsMusicAlbum(fileSystemInfo.FullName, directoryService))
{
// stop once we see a music album
state.Stop();
}
});
return !result.IsCompleted ? new MusicArtist() : null;
} }
} }
} }

View File

@ -50,7 +50,8 @@ namespace Emby.Server.Implementations.Library.Resolvers.Books
var fileExtension = Path.GetExtension(f.FullName) ?? var fileExtension = Path.GetExtension(f.FullName) ??
string.Empty; string.Empty;
return _validExtensions.Contains(fileExtension, return _validExtensions.Contains(
fileExtension,
StringComparer StringComparer
.OrdinalIgnoreCase); .OrdinalIgnoreCase);
}).ToList(); }).ToList();

View File

@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.LiveTv
return new[] return new[]
{ {
// Every so often // Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
}; };
} }

View File

@ -85,7 +85,6 @@
"ItemAddedWithName": "{0} is in die versameling", "ItemAddedWithName": "{0} is in die versameling",
"HomeVideos": "Tuis opnames", "HomeVideos": "Tuis opnames",
"HeaderRecordingGroups": "Groep Opnames", "HeaderRecordingGroups": "Groep Opnames",
"HeaderCameraUploads": "Kamera Oplaai",
"Genres": "Genres", "Genres": "Genres",
"FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}", "FailedLoginAttemptWithUserName": "Mislukte aansluiting van {0}",
"ChapterNameValue": "Hoofstuk", "ChapterNameValue": "Hoofstuk",

View File

@ -16,7 +16,6 @@
"Folders": "المجلدات", "Folders": "المجلدات",
"Genres": "التضنيفات", "Genres": "التضنيفات",
"HeaderAlbumArtists": "فناني الألبومات", "HeaderAlbumArtists": "فناني الألبومات",
"HeaderCameraUploads": "تحميلات الكاميرا",
"HeaderContinueWatching": "استئناف", "HeaderContinueWatching": "استئناف",
"HeaderFavoriteAlbums": "الألبومات المفضلة", "HeaderFavoriteAlbums": "الألبومات المفضلة",
"HeaderFavoriteArtists": "الفنانون المفضلون", "HeaderFavoriteArtists": "الفنانون المفضلون",

View File

@ -16,7 +16,6 @@
"Folders": "Папки", "Folders": "Папки",
"Genres": "Жанрове", "Genres": "Жанрове",
"HeaderAlbumArtists": "Изпълнители на албуми", "HeaderAlbumArtists": "Изпълнители на албуми",
"HeaderCameraUploads": "Качени от камера",
"HeaderContinueWatching": "Продължаване на гледането", "HeaderContinueWatching": "Продължаване на гледането",
"HeaderFavoriteAlbums": "Любими албуми", "HeaderFavoriteAlbums": "Любими албуми",
"HeaderFavoriteArtists": "Любими изпълнители", "HeaderFavoriteArtists": "Любими изпълнители",

View File

@ -14,7 +14,6 @@
"HeaderFavoriteArtists": "প্রিয় শিল্পীরা", "HeaderFavoriteArtists": "প্রিয় শিল্পীরা",
"HeaderFavoriteAlbums": "প্রিয় এলবামগুলো", "HeaderFavoriteAlbums": "প্রিয় এলবামগুলো",
"HeaderContinueWatching": "দেখতে থাকুন", "HeaderContinueWatching": "দেখতে থাকুন",
"HeaderCameraUploads": "ক্যামেরার আপলোড সমূহ",
"HeaderAlbumArtists": "এলবাম শিল্পী", "HeaderAlbumArtists": "এলবাম শিল্পী",
"Genres": "জেনার", "Genres": "জেনার",
"Folders": "ফোল্ডারগুলো", "Folders": "ফোল্ডারগুলো",

View File

@ -16,7 +16,6 @@
"Folders": "Carpetes", "Folders": "Carpetes",
"Genres": "Gèneres", "Genres": "Gèneres",
"HeaderAlbumArtists": "Artistes del Àlbum", "HeaderAlbumArtists": "Artistes del Àlbum",
"HeaderCameraUploads": "Pujades de Càmera",
"HeaderContinueWatching": "Continua Veient", "HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits", "HeaderFavoriteAlbums": "Àlbums Preferits",
"HeaderFavoriteArtists": "Artistes Preferits", "HeaderFavoriteArtists": "Artistes Preferits",

View File

@ -16,7 +16,6 @@
"Folders": "Složky", "Folders": "Složky",
"Genres": "Žánry", "Genres": "Žánry",
"HeaderAlbumArtists": "Umělci alba", "HeaderAlbumArtists": "Umělci alba",
"HeaderCameraUploads": "Nahrané fotografie",
"HeaderContinueWatching": "Pokračovat ve sledování", "HeaderContinueWatching": "Pokračovat ve sledování",
"HeaderFavoriteAlbums": "Oblíbená alba", "HeaderFavoriteAlbums": "Oblíbená alba",
"HeaderFavoriteArtists": "Oblíbení interpreti", "HeaderFavoriteArtists": "Oblíbení interpreti",

View File

@ -16,7 +16,6 @@
"Folders": "Mapper", "Folders": "Mapper",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumkunstnere", "HeaderAlbumArtists": "Albumkunstnere",
"HeaderCameraUploads": "Kamera Uploads",
"HeaderContinueWatching": "Fortsæt Afspilning", "HeaderContinueWatching": "Fortsæt Afspilning",
"HeaderFavoriteAlbums": "Favoritalbummer", "HeaderFavoriteAlbums": "Favoritalbummer",
"HeaderFavoriteArtists": "Favoritkunstnere", "HeaderFavoriteArtists": "Favoritkunstnere",

View File

@ -16,7 +16,6 @@
"Folders": "Verzeichnisse", "Folders": "Verzeichnisse",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album-Interpreten", "HeaderAlbumArtists": "Album-Interpreten",
"HeaderCameraUploads": "Kamera-Uploads",
"HeaderContinueWatching": "Fortsetzen", "HeaderContinueWatching": "Fortsetzen",
"HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblings-Interpreten", "HeaderFavoriteArtists": "Lieblings-Interpreten",

View File

@ -16,7 +16,6 @@
"Folders": "Φάκελοι", "Folders": "Φάκελοι",
"Genres": "Είδη", "Genres": "Είδη",
"HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ", "HeaderAlbumArtists": "Καλλιτέχνες του Άλμπουμ",
"HeaderCameraUploads": "Μεταφορτώσεις Κάμερας",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση", "HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ", "HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες", "HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",

View File

@ -16,7 +16,6 @@
"Folders": "Folders", "Folders": "Folders",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album Artists", "HeaderAlbumArtists": "Album Artists",
"HeaderCameraUploads": "Camera Uploads",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favourite Albums", "HeaderFavoriteAlbums": "Favourite Albums",
"HeaderFavoriteArtists": "Favourite Artists", "HeaderFavoriteArtists": "Favourite Artists",

View File

@ -16,7 +16,6 @@
"Folders": "Folders", "Folders": "Folders",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album Artists", "HeaderAlbumArtists": "Album Artists",
"HeaderCameraUploads": "Camera Uploads",
"HeaderContinueWatching": "Continue Watching", "HeaderContinueWatching": "Continue Watching",
"HeaderFavoriteAlbums": "Favorite Albums", "HeaderFavoriteAlbums": "Favorite Albums",
"HeaderFavoriteArtists": "Favorite Artists", "HeaderFavoriteArtists": "Favorite Artists",

View File

@ -16,7 +16,6 @@
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas de álbum", "HeaderAlbumArtists": "Artistas de álbum",
"HeaderCameraUploads": "Subidas de cámara",
"HeaderContinueWatching": "Seguir viendo", "HeaderContinueWatching": "Seguir viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",

View File

@ -16,7 +16,6 @@
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbumArtists": "Artistas del álbum",
"HeaderCameraUploads": "Subidas desde la cámara",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",

View File

@ -16,7 +16,6 @@
"Folders": "Carpetas", "Folders": "Carpetas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas del álbum", "HeaderAlbumArtists": "Artistas del álbum",
"HeaderCameraUploads": "Subidas desde la cámara",
"HeaderContinueWatching": "Continuar viendo", "HeaderContinueWatching": "Continuar viendo",
"HeaderFavoriteAlbums": "Álbumes favoritos", "HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",

View File

@ -105,7 +105,6 @@
"Inherit": "Heredar", "Inherit": "Heredar",
"HomeVideos": "Videos caseros", "HomeVideos": "Videos caseros",
"HeaderRecordingGroups": "Grupos de grabación", "HeaderRecordingGroups": "Grupos de grabación",
"HeaderCameraUploads": "Subidas desde la cámara",
"FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}", "FailedLoginAttemptWithUserName": "Intento fallido de inicio de sesión desde {0}",
"DeviceOnlineWithName": "{0} está conectado", "DeviceOnlineWithName": "{0} está conectado",
"DeviceOfflineWithName": "{0} se ha desconectado", "DeviceOfflineWithName": "{0} se ha desconectado",

View File

@ -12,7 +12,6 @@
"Application": "Aplicación", "Application": "Aplicación",
"AppDeviceValues": "App: {0}, Dispositivo: {1}", "AppDeviceValues": "App: {0}, Dispositivo: {1}",
"HeaderContinueWatching": "Continuar Viendo", "HeaderContinueWatching": "Continuar Viendo",
"HeaderCameraUploads": "Subidas de Cámara",
"HeaderAlbumArtists": "Artistas del Álbum", "HeaderAlbumArtists": "Artistas del Álbum",
"Genres": "Géneros", "Genres": "Géneros",
"Folders": "Carpetas", "Folders": "Carpetas",

View File

@ -16,7 +16,6 @@
"Folders": "پوشه‌ها", "Folders": "پوشه‌ها",
"Genres": "ژانرها", "Genres": "ژانرها",
"HeaderAlbumArtists": "هنرمندان آلبوم", "HeaderAlbumArtists": "هنرمندان آلبوم",
"HeaderCameraUploads": "آپلودهای دوربین",
"HeaderContinueWatching": "ادامه تماشا", "HeaderContinueWatching": "ادامه تماشا",
"HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه", "HeaderFavoriteAlbums": "آلبوم‌های مورد علاقه",
"HeaderFavoriteArtists": "هنرمندان مورد علاقه", "HeaderFavoriteArtists": "هنرمندان مورد علاقه",

View File

@ -24,7 +24,6 @@
"HeaderFavoriteSongs": "Lempikappaleet", "HeaderFavoriteSongs": "Lempikappaleet",
"HeaderFavoriteShows": "Lempisarjat", "HeaderFavoriteShows": "Lempisarjat",
"HeaderFavoriteEpisodes": "Lempijaksot", "HeaderFavoriteEpisodes": "Lempijaksot",
"HeaderCameraUploads": "Kamerasta Lähetetyt",
"HeaderFavoriteArtists": "Lempiartistit", "HeaderFavoriteArtists": "Lempiartistit",
"HeaderFavoriteAlbums": "Lempialbumit", "HeaderFavoriteAlbums": "Lempialbumit",
"HeaderContinueWatching": "Jatka katsomista", "HeaderContinueWatching": "Jatka katsomista",

View File

@ -73,7 +73,6 @@
"HeaderFavoriteArtists": "Paboritong Artista", "HeaderFavoriteArtists": "Paboritong Artista",
"HeaderFavoriteAlbums": "Paboritong Albums", "HeaderFavoriteAlbums": "Paboritong Albums",
"HeaderContinueWatching": "Ituloy Manood", "HeaderContinueWatching": "Ituloy Manood",
"HeaderCameraUploads": "Camera Uploads",
"HeaderAlbumArtists": "Artista ng Album", "HeaderAlbumArtists": "Artista ng Album",
"Genres": "Kategorya", "Genres": "Kategorya",
"Folders": "Folders", "Folders": "Folders",

View File

@ -16,7 +16,6 @@
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artistes de l'album", "HeaderAlbumArtists": "Artistes de l'album",
"HeaderCameraUploads": "Photos transférées",
"HeaderContinueWatching": "Continuer à regarder", "HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes favoris", "HeaderFavoriteArtists": "Artistes favoris",

View File

@ -16,7 +16,6 @@
"Folders": "Dossiers", "Folders": "Dossiers",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Artistes", "HeaderAlbumArtists": "Artistes",
"HeaderCameraUploads": "Photos transférées",
"HeaderContinueWatching": "Continuer à regarder", "HeaderContinueWatching": "Continuer à regarder",
"HeaderFavoriteAlbums": "Albums favoris", "HeaderFavoriteAlbums": "Albums favoris",
"HeaderFavoriteArtists": "Artistes préférés", "HeaderFavoriteArtists": "Artistes préférés",

View File

@ -16,7 +16,6 @@
"Folders": "Ordner", "Folders": "Ordner",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Album-Künstler", "HeaderAlbumArtists": "Album-Künstler",
"HeaderCameraUploads": "Kamera-Uploads",
"HeaderContinueWatching": "weiter schauen", "HeaderContinueWatching": "weiter schauen",
"HeaderFavoriteAlbums": "Lieblingsalben", "HeaderFavoriteAlbums": "Lieblingsalben",
"HeaderFavoriteArtists": "Lieblings-Künstler", "HeaderFavoriteArtists": "Lieblings-Künstler",

View File

@ -16,7 +16,6 @@
"Folders": "תיקיות", "Folders": "תיקיות",
"Genres": "ז'אנרים", "Genres": "ז'אנרים",
"HeaderAlbumArtists": "אמני האלבום", "HeaderAlbumArtists": "אמני האלבום",
"HeaderCameraUploads": "העלאות ממצלמה",
"HeaderContinueWatching": "המשך לצפות", "HeaderContinueWatching": "המשך לצפות",
"HeaderFavoriteAlbums": "אלבומים מועדפים", "HeaderFavoriteAlbums": "אלבומים מועדפים",
"HeaderFavoriteArtists": "אמנים מועדפים", "HeaderFavoriteArtists": "אמנים מועדפים",

View File

@ -0,0 +1,3 @@
{
"Albums": "आल्बुम्"
}

View File

@ -16,7 +16,6 @@
"Folders": "Mape", "Folders": "Mape",
"Genres": "Žanrovi", "Genres": "Žanrovi",
"HeaderAlbumArtists": "Izvođači na albumu", "HeaderAlbumArtists": "Izvođači na albumu",
"HeaderCameraUploads": "Uvoz sa kamere",
"HeaderContinueWatching": "Nastavi gledati", "HeaderContinueWatching": "Nastavi gledati",
"HeaderFavoriteAlbums": "Omiljeni albumi", "HeaderFavoriteAlbums": "Omiljeni albumi",
"HeaderFavoriteArtists": "Omiljeni izvođači", "HeaderFavoriteArtists": "Omiljeni izvođači",

View File

@ -16,7 +16,6 @@
"Folders": "Könyvtárak", "Folders": "Könyvtárak",
"Genres": "Műfajok", "Genres": "Műfajok",
"HeaderAlbumArtists": "Album előadók", "HeaderAlbumArtists": "Album előadók",
"HeaderCameraUploads": "Kamera feltöltések",
"HeaderContinueWatching": "Megtekintés folytatása", "HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok", "HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteArtists": "Kedvenc előadók", "HeaderFavoriteArtists": "Kedvenc előadók",

View File

@ -20,7 +20,6 @@
"HeaderFavoriteArtists": "Artis Favorit", "HeaderFavoriteArtists": "Artis Favorit",
"HeaderFavoriteAlbums": "Album Favorit", "HeaderFavoriteAlbums": "Album Favorit",
"HeaderContinueWatching": "Lanjut Menonton", "HeaderContinueWatching": "Lanjut Menonton",
"HeaderCameraUploads": "Unggahan Kamera",
"HeaderAlbumArtists": "Album Artis", "HeaderAlbumArtists": "Album Artis",
"Genres": "Aliran", "Genres": "Aliran",
"Folders": "Folder", "Folders": "Folder",

View File

@ -13,7 +13,6 @@
"HeaderFavoriteArtists": "Uppáhalds Listamenn", "HeaderFavoriteArtists": "Uppáhalds Listamenn",
"HeaderFavoriteAlbums": "Uppáhalds Plötur", "HeaderFavoriteAlbums": "Uppáhalds Plötur",
"HeaderContinueWatching": "Halda áfram að horfa", "HeaderContinueWatching": "Halda áfram að horfa",
"HeaderCameraUploads": "Myndavéla upphal",
"HeaderAlbumArtists": "Höfundur plötu", "HeaderAlbumArtists": "Höfundur plötu",
"Genres": "Tegundir", "Genres": "Tegundir",
"Folders": "Möppur", "Folders": "Möppur",

View File

@ -16,7 +16,6 @@
"Folders": "Cartelle", "Folders": "Cartelle",
"Genres": "Generi", "Genres": "Generi",
"HeaderAlbumArtists": "Artisti degli Album", "HeaderAlbumArtists": "Artisti degli Album",
"HeaderCameraUploads": "Caricamenti Fotocamera",
"HeaderContinueWatching": "Continua a guardare", "HeaderContinueWatching": "Continua a guardare",
"HeaderFavoriteAlbums": "Album Preferiti", "HeaderFavoriteAlbums": "Album Preferiti",
"HeaderFavoriteArtists": "Artisti Preferiti", "HeaderFavoriteArtists": "Artisti Preferiti",

View File

@ -16,7 +16,6 @@
"Folders": "フォルダー", "Folders": "フォルダー",
"Genres": "ジャンル", "Genres": "ジャンル",
"HeaderAlbumArtists": "アルバムアーティスト", "HeaderAlbumArtists": "アルバムアーティスト",
"HeaderCameraUploads": "カメラアップロード",
"HeaderContinueWatching": "視聴を続ける", "HeaderContinueWatching": "視聴を続ける",
"HeaderFavoriteAlbums": "お気に入りのアルバム", "HeaderFavoriteAlbums": "お気に入りのアルバム",
"HeaderFavoriteArtists": "お気に入りのアーティスト", "HeaderFavoriteArtists": "お気に入りのアーティスト",

View File

@ -16,7 +16,6 @@
"Folders": "Qaltalar", "Folders": "Qaltalar",
"Genres": "Janrlar", "Genres": "Janrlar",
"HeaderAlbumArtists": "Álbom oryndaýshylary", "HeaderAlbumArtists": "Álbom oryndaýshylary",
"HeaderCameraUploads": "Kameradan júktelgender",
"HeaderContinueWatching": "Qaraýdy jalǵastyrý", "HeaderContinueWatching": "Qaraýdy jalǵastyrý",
"HeaderFavoriteAlbums": "Tańdaýly álbomdar", "HeaderFavoriteAlbums": "Tańdaýly álbomdar",
"HeaderFavoriteArtists": "Tańdaýly oryndaýshylar", "HeaderFavoriteArtists": "Tańdaýly oryndaýshylar",

View File

@ -16,7 +16,6 @@
"Folders": "폴더", "Folders": "폴더",
"Genres": "장르", "Genres": "장르",
"HeaderAlbumArtists": "앨범 아티스트", "HeaderAlbumArtists": "앨범 아티스트",
"HeaderCameraUploads": "카메라 업로드",
"HeaderContinueWatching": "계속 시청하기", "HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범", "HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트", "HeaderFavoriteArtists": "즐겨찾는 아티스트",

View File

@ -16,7 +16,6 @@
"Folders": "Katalogai", "Folders": "Katalogai",
"Genres": "Žanrai", "Genres": "Žanrai",
"HeaderAlbumArtists": "Albumo atlikėjai", "HeaderAlbumArtists": "Albumo atlikėjai",
"HeaderCameraUploads": "Kameros",
"HeaderContinueWatching": "Žiūrėti toliau", "HeaderContinueWatching": "Žiūrėti toliau",
"HeaderFavoriteAlbums": "Mėgstami Albumai", "HeaderFavoriteAlbums": "Mėgstami Albumai",
"HeaderFavoriteArtists": "Mėgstami Atlikėjai", "HeaderFavoriteArtists": "Mėgstami Atlikėjai",

View File

@ -72,7 +72,6 @@
"ItemAddedWithName": "{0} tika pievienots bibliotēkai", "ItemAddedWithName": "{0} tika pievienots bibliotēkai",
"HeaderLiveTV": "Tiešraides TV", "HeaderLiveTV": "Tiešraides TV",
"HeaderContinueWatching": "Turpināt Skatīšanos", "HeaderContinueWatching": "Turpināt Skatīšanos",
"HeaderCameraUploads": "Kameras augšupielādes",
"HeaderAlbumArtists": "Albumu Izpildītāji", "HeaderAlbumArtists": "Albumu Izpildītāji",
"Genres": "Žanri", "Genres": "Žanri",
"Folders": "Mapes", "Folders": "Mapes",

View File

@ -51,7 +51,6 @@
"HeaderFavoriteArtists": "Омилени Изведувачи", "HeaderFavoriteArtists": "Омилени Изведувачи",
"HeaderFavoriteAlbums": "Омилени Албуми", "HeaderFavoriteAlbums": "Омилени Албуми",
"HeaderContinueWatching": "Продолжи со гледање", "HeaderContinueWatching": "Продолжи со гледање",
"HeaderCameraUploads": "Поставувања од камера",
"HeaderAlbumArtists": "Изведувачи од Албуми", "HeaderAlbumArtists": "Изведувачи од Албуми",
"Genres": "Жанрови", "Genres": "Жанрови",
"Folders": "Папки", "Folders": "Папки",

View File

@ -54,7 +54,6 @@
"ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले", "ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
"HomeVideos": "घरचे व्हिडीयो", "HomeVideos": "घरचे व्हिडीयो",
"HeaderRecordingGroups": "रेकॉर्डिंग गट", "HeaderRecordingGroups": "रेकॉर्डिंग गट",
"HeaderCameraUploads": "कॅमेरा अपलोड",
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे", "CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
"Application": "अ‍ॅप्लिकेशन", "Application": "अ‍ॅप्लिकेशन",
"AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}", "AppDeviceValues": "अ‍ॅप: {0}, यंत्र: {1}",

View File

@ -16,7 +16,6 @@
"Folders": "Fail-fail", "Folders": "Fail-fail",
"Genres": "Genre-genre", "Genres": "Genre-genre",
"HeaderAlbumArtists": "Album Artis-artis", "HeaderAlbumArtists": "Album Artis-artis",
"HeaderCameraUploads": "Muatnaik Kamera",
"HeaderContinueWatching": "Terus Menonton", "HeaderContinueWatching": "Terus Menonton",
"HeaderFavoriteAlbums": "Album-album Kegemaran", "HeaderFavoriteAlbums": "Album-album Kegemaran",
"HeaderFavoriteArtists": "Artis-artis Kegemaran", "HeaderFavoriteArtists": "Artis-artis Kegemaran",

View File

@ -16,7 +16,6 @@
"Folders": "Mapper", "Folders": "Mapper",
"Genres": "Sjangre", "Genres": "Sjangre",
"HeaderAlbumArtists": "Albumartister", "HeaderAlbumArtists": "Albumartister",
"HeaderCameraUploads": "Kameraopplastinger",
"HeaderContinueWatching": "Fortsett å se", "HeaderContinueWatching": "Fortsett å se",
"HeaderFavoriteAlbums": "Favorittalbum", "HeaderFavoriteAlbums": "Favorittalbum",
"HeaderFavoriteArtists": "Favorittartister", "HeaderFavoriteArtists": "Favorittartister",

View File

@ -41,7 +41,6 @@
"HeaderFavoriteArtists": "मनपर्ने कलाकारहरू", "HeaderFavoriteArtists": "मनपर्ने कलाकारहरू",
"HeaderFavoriteAlbums": "मनपर्ने एल्बमहरू", "HeaderFavoriteAlbums": "मनपर्ने एल्बमहरू",
"HeaderContinueWatching": "हेर्न जारी राख्नुहोस्", "HeaderContinueWatching": "हेर्न जारी राख्नुहोस्",
"HeaderCameraUploads": "क्यामेरा अपलोडहरू",
"HeaderAlbumArtists": "एल्बमका कलाकारहरू", "HeaderAlbumArtists": "एल्बमका कलाकारहरू",
"Genres": "विधाहरू", "Genres": "विधाहरू",
"Folders": "फोल्डरहरू", "Folders": "फोल्डरहरू",

View File

@ -16,7 +16,6 @@
"Folders": "Mappen", "Folders": "Mappen",
"Genres": "Genres", "Genres": "Genres",
"HeaderAlbumArtists": "Albumartiesten", "HeaderAlbumArtists": "Albumartiesten",
"HeaderCameraUploads": "Camera-uploads",
"HeaderContinueWatching": "Kijken hervatten", "HeaderContinueWatching": "Kijken hervatten",
"HeaderFavoriteAlbums": "Favoriete albums", "HeaderFavoriteAlbums": "Favoriete albums",
"HeaderFavoriteArtists": "Favoriete artiesten", "HeaderFavoriteArtists": "Favoriete artiesten",

View File

@ -19,7 +19,6 @@
"HeaderFavoriteArtists": "Favoritt Artistar", "HeaderFavoriteArtists": "Favoritt Artistar",
"HeaderFavoriteAlbums": "Favoritt Album", "HeaderFavoriteAlbums": "Favoritt Album",
"HeaderContinueWatching": "Fortsett å sjå", "HeaderContinueWatching": "Fortsett å sjå",
"HeaderCameraUploads": "Kamera Opplastingar",
"HeaderAlbumArtists": "Album Artist", "HeaderAlbumArtists": "Album Artist",
"Genres": "Sjangrar", "Genres": "Sjangrar",
"Folders": "Mapper", "Folders": "Mapper",

View File

@ -16,7 +16,6 @@
"Folders": "Foldery", "Folders": "Foldery",
"Genres": "Gatunki", "Genres": "Gatunki",
"HeaderAlbumArtists": "Wykonawcy albumów", "HeaderAlbumArtists": "Wykonawcy albumów",
"HeaderCameraUploads": "Przekazane obrazy",
"HeaderContinueWatching": "Kontynuuj odtwarzanie", "HeaderContinueWatching": "Kontynuuj odtwarzanie",
"HeaderFavoriteAlbums": "Ulubione albumy", "HeaderFavoriteAlbums": "Ulubione albumy",
"HeaderFavoriteArtists": "Ulubieni wykonawcy", "HeaderFavoriteArtists": "Ulubieni wykonawcy",

View File

@ -16,7 +16,6 @@
"Folders": "Pastas", "Folders": "Pastas",
"Genres": "Gêneros", "Genres": "Gêneros",
"HeaderAlbumArtists": "Artistas do Álbum", "HeaderAlbumArtists": "Artistas do Álbum",
"HeaderCameraUploads": "Envios da Câmera",
"HeaderContinueWatching": "Continuar Assistindo", "HeaderContinueWatching": "Continuar Assistindo",
"HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas favoritos", "HeaderFavoriteArtists": "Artistas favoritos",

View File

@ -16,7 +16,6 @@
"Folders": "Pastas", "Folders": "Pastas",
"Genres": "Géneros", "Genres": "Géneros",
"HeaderAlbumArtists": "Artistas do Álbum", "HeaderAlbumArtists": "Artistas do Álbum",
"HeaderCameraUploads": "Envios a partir da câmara",
"HeaderContinueWatching": "Continuar a Ver", "HeaderContinueWatching": "Continuar a Ver",
"HeaderFavoriteAlbums": "Álbuns Favoritos", "HeaderFavoriteAlbums": "Álbuns Favoritos",
"HeaderFavoriteArtists": "Artistas Favoritos", "HeaderFavoriteArtists": "Artistas Favoritos",
@ -26,7 +25,7 @@
"HeaderLiveTV": "TV em Direto", "HeaderLiveTV": "TV em Direto",
"HeaderNextUp": "A Seguir", "HeaderNextUp": "A Seguir",
"HeaderRecordingGroups": "Grupos de Gravação", "HeaderRecordingGroups": "Grupos de Gravação",
"HomeVideos": "Videos caseiros", "HomeVideos": "Vídeos Caseiros",
"Inherit": "Herdar", "Inherit": "Herdar",
"ItemAddedWithName": "{0} foi adicionado à biblioteca", "ItemAddedWithName": "{0} foi adicionado à biblioteca",
"ItemRemovedWithName": "{0} foi removido da biblioteca", "ItemRemovedWithName": "{0} foi removido da biblioteca",

View File

@ -83,7 +83,6 @@
"Playlists": "Listas de Reprodução", "Playlists": "Listas de Reprodução",
"Photos": "Fotografias", "Photos": "Fotografias",
"Movies": "Filmes", "Movies": "Filmes",
"HeaderCameraUploads": "Carregamentos a partir da câmara",
"FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}", "FailedLoginAttemptWithUserName": "Tentativa de ligação falhada a partir de {0}",
"DeviceOnlineWithName": "{0} está connectado", "DeviceOnlineWithName": "{0} está connectado",
"DeviceOfflineWithName": "{0} desconectou-se", "DeviceOfflineWithName": "{0} desconectou-se",

View File

@ -74,7 +74,6 @@
"HeaderFavoriteArtists": "Artiști Favoriți", "HeaderFavoriteArtists": "Artiști Favoriți",
"HeaderFavoriteAlbums": "Albume Favorite", "HeaderFavoriteAlbums": "Albume Favorite",
"HeaderContinueWatching": "Vizionează în continuare", "HeaderContinueWatching": "Vizionează în continuare",
"HeaderCameraUploads": "Incărcări Cameră Foto",
"HeaderAlbumArtists": "Album Artiști", "HeaderAlbumArtists": "Album Artiști",
"Genres": "Genuri", "Genres": "Genuri",
"Folders": "Dosare", "Folders": "Dosare",

View File

@ -16,7 +16,6 @@
"Folders": "Папки", "Folders": "Папки",
"Genres": "Жанры", "Genres": "Жанры",
"HeaderAlbumArtists": "Исполнители альбома", "HeaderAlbumArtists": "Исполнители альбома",
"HeaderCameraUploads": "Камеры",
"HeaderContinueWatching": "Продолжение просмотра", "HeaderContinueWatching": "Продолжение просмотра",
"HeaderFavoriteAlbums": "Избранные альбомы", "HeaderFavoriteAlbums": "Избранные альбомы",
"HeaderFavoriteArtists": "Избранные исполнители", "HeaderFavoriteArtists": "Избранные исполнители",

View File

@ -16,7 +16,6 @@
"Folders": "Priečinky", "Folders": "Priečinky",
"Genres": "Žánre", "Genres": "Žánre",
"HeaderAlbumArtists": "Umelci albumu", "HeaderAlbumArtists": "Umelci albumu",
"HeaderCameraUploads": "Nahrané fotografie",
"HeaderContinueWatching": "Pokračovať v pozeraní", "HeaderContinueWatching": "Pokračovať v pozeraní",
"HeaderFavoriteAlbums": "Obľúbené albumy", "HeaderFavoriteAlbums": "Obľúbené albumy",
"HeaderFavoriteArtists": "Obľúbení umelci", "HeaderFavoriteArtists": "Obľúbení umelci",

View File

@ -16,7 +16,6 @@
"Folders": "Mape", "Folders": "Mape",
"Genres": "Zvrsti", "Genres": "Zvrsti",
"HeaderAlbumArtists": "Izvajalci albuma", "HeaderAlbumArtists": "Izvajalci albuma",
"HeaderCameraUploads": "Posnetki kamere",
"HeaderContinueWatching": "Nadaljuj gledanje", "HeaderContinueWatching": "Nadaljuj gledanje",
"HeaderFavoriteAlbums": "Priljubljeni albumi", "HeaderFavoriteAlbums": "Priljubljeni albumi",
"HeaderFavoriteArtists": "Priljubljeni izvajalci", "HeaderFavoriteArtists": "Priljubljeni izvajalci",

View File

@ -96,7 +96,6 @@
"HeaderFavoriteArtists": "Artistët e preferuar", "HeaderFavoriteArtists": "Artistët e preferuar",
"HeaderFavoriteAlbums": "Albumet e preferuar", "HeaderFavoriteAlbums": "Albumet e preferuar",
"HeaderContinueWatching": "Vazhdo të shikosh", "HeaderContinueWatching": "Vazhdo të shikosh",
"HeaderCameraUploads": "Ngarkimet nga Kamera",
"HeaderAlbumArtists": "Artistët e albumeve", "HeaderAlbumArtists": "Artistët e albumeve",
"Genres": "Zhanre", "Genres": "Zhanre",
"Folders": "Dosje", "Folders": "Dosje",
@ -113,5 +112,5 @@
"Artists": "Artistë", "Artists": "Artistë",
"Application": "Aplikacioni", "Application": "Aplikacioni",
"AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}", "AppDeviceValues": "Aplikacioni: {0}, Pajisja: {1}",
"Albums": "Albumet" "Albums": "Albume"
} }

View File

@ -74,7 +74,6 @@
"HeaderFavoriteArtists": "Омиљени извођачи", "HeaderFavoriteArtists": "Омиљени извођачи",
"HeaderFavoriteAlbums": "Омиљени албуми", "HeaderFavoriteAlbums": "Омиљени албуми",
"HeaderContinueWatching": "Настави гледање", "HeaderContinueWatching": "Настави гледање",
"HeaderCameraUploads": "Слања са камере",
"HeaderAlbumArtists": "Извођачи албума", "HeaderAlbumArtists": "Извођачи албума",
"Genres": "Жанрови", "Genres": "Жанрови",
"Folders": "Фасцикле", "Folders": "Фасцикле",

View File

@ -9,14 +9,13 @@
"Channels": "Kanaler", "Channels": "Kanaler",
"ChapterNameValue": "Kapitel {0}", "ChapterNameValue": "Kapitel {0}",
"Collections": "Samlingar", "Collections": "Samlingar",
"DeviceOfflineWithName": "{0} har kopplat från", "DeviceOfflineWithName": "{0} har kopplat ner",
"DeviceOnlineWithName": "{0} är ansluten", "DeviceOnlineWithName": "{0} är ansluten",
"FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}", "FailedLoginAttemptWithUserName": "Misslyckat inloggningsförsök från {0}",
"Favorites": "Favoriter", "Favorites": "Favoriter",
"Folders": "Mappar", "Folders": "Mappar",
"Genres": "Genrer", "Genres": "Genrer",
"HeaderAlbumArtists": "Albumartister", "HeaderAlbumArtists": "Albumartister",
"HeaderCameraUploads": "Kamerauppladdningar",
"HeaderContinueWatching": "Fortsätt kolla", "HeaderContinueWatching": "Fortsätt kolla",
"HeaderFavoriteAlbums": "Favoritalbum", "HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister", "HeaderFavoriteArtists": "Favoritartister",

View File

@ -20,7 +20,6 @@
"MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது", "MessageApplicationUpdated": "ஜெல்லிஃபின் சேவையகம் புதுப்பிக்கப்பட்டது",
"Inherit": "மரபுரிமையாகப் பெறு", "Inherit": "மரபுரிமையாகப் பெறு",
"HeaderRecordingGroups": "பதிவு குழுக்கள்", "HeaderRecordingGroups": "பதிவு குழுக்கள்",
"HeaderCameraUploads": "புகைப்பட பதிவேற்றங்கள்",
"Folders": "கோப்புறைகள்", "Folders": "கோப்புறைகள்",
"FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது", "FailedLoginAttemptWithUserName": "{0} இலிருந்து உள்நுழைவு முயற்சி தோல்வியடைந்தது",
"DeviceOnlineWithName": "{0} இணைக்கப்பட்டது", "DeviceOnlineWithName": "{0} இணைக்கப்பட்டது",

View File

@ -50,7 +50,6 @@
"HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ", "HeaderFavoriteArtists": "ศิลปินที่ชื่นชอบ",
"HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ", "HeaderFavoriteAlbums": "อัมบั้มที่ชื่นชอบ",
"HeaderContinueWatching": "ดูต่อ", "HeaderContinueWatching": "ดูต่อ",
"HeaderCameraUploads": "อัปโหลดรูปถ่าย",
"HeaderAlbumArtists": "อัลบั้มศิลปิน", "HeaderAlbumArtists": "อัลบั้มศิลปิน",
"Genres": "ประเภท", "Genres": "ประเภท",
"Folders": "โฟลเดอร์", "Folders": "โฟลเดอร์",

View File

@ -16,7 +16,6 @@
"Folders": "Klasörler", "Folders": "Klasörler",
"Genres": "Türler", "Genres": "Türler",
"HeaderAlbumArtists": "Albüm Sanatçıları", "HeaderAlbumArtists": "Albüm Sanatçıları",
"HeaderCameraUploads": "Kamera Yüklemeleri",
"HeaderContinueWatching": "İzlemeye Devam Et", "HeaderContinueWatching": "İzlemeye Devam Et",
"HeaderFavoriteAlbums": "Favori Albümler", "HeaderFavoriteAlbums": "Favori Albümler",
"HeaderFavoriteArtists": "Favori Sanatçılar", "HeaderFavoriteArtists": "Favori Sanatçılar",

View File

@ -16,7 +16,6 @@
"HeaderFavoriteArtists": "Улюблені виконавці", "HeaderFavoriteArtists": "Улюблені виконавці",
"HeaderFavoriteAlbums": "Улюблені альбоми", "HeaderFavoriteAlbums": "Улюблені альбоми",
"HeaderContinueWatching": "Продовжити перегляд", "HeaderContinueWatching": "Продовжити перегляд",
"HeaderCameraUploads": "Завантажено з камери",
"HeaderAlbumArtists": "Виконавці альбому", "HeaderAlbumArtists": "Виконавці альбому",
"Genres": "Жанри", "Genres": "Жанри",
"Folders": "Каталоги", "Folders": "Каталоги",

View File

@ -105,7 +105,6 @@
"Inherit": "وراثت میں", "Inherit": "وراثت میں",
"HomeVideos": "ہوم ویڈیو", "HomeVideos": "ہوم ویڈیو",
"HeaderRecordingGroups": "ریکارڈنگ گروپس", "HeaderRecordingGroups": "ریکارڈنگ گروپس",
"HeaderCameraUploads": "کیمرہ اپلوڈز",
"FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}", "FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے", "DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے", "DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",

View File

@ -103,7 +103,6 @@
"HeaderFavoriteEpisodes": "Tập Phim Yêu Thích", "HeaderFavoriteEpisodes": "Tập Phim Yêu Thích",
"HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích", "HeaderFavoriteArtists": "Nghệ Sĩ Yêu Thích",
"HeaderFavoriteAlbums": "Album Ưa Thích", "HeaderFavoriteAlbums": "Album Ưa Thích",
"HeaderCameraUploads": "Máy Ảnh Tải Lên",
"FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}", "FailedLoginAttemptWithUserName": "Nỗ lực đăng nhập thất bại từ {0}",
"DeviceOnlineWithName": "{0} đã kết nối", "DeviceOnlineWithName": "{0} đã kết nối",
"DeviceOfflineWithName": "{0} đã ngắt kết nối", "DeviceOfflineWithName": "{0} đã ngắt kết nối",

View File

@ -16,7 +16,6 @@
"Folders": "文件夹", "Folders": "文件夹",
"Genres": "风格", "Genres": "风格",
"HeaderAlbumArtists": "专辑作家", "HeaderAlbumArtists": "专辑作家",
"HeaderCameraUploads": "相机上传",
"HeaderContinueWatching": "继续观影", "HeaderContinueWatching": "继续观影",
"HeaderFavoriteAlbums": "收藏的专辑", "HeaderFavoriteAlbums": "收藏的专辑",
"HeaderFavoriteArtists": "最爱的艺术家", "HeaderFavoriteArtists": "最爱的艺术家",

View File

@ -16,7 +16,6 @@
"Folders": "檔案夾", "Folders": "檔案夾",
"Genres": "風格", "Genres": "風格",
"HeaderAlbumArtists": "專輯藝人", "HeaderAlbumArtists": "專輯藝人",
"HeaderCameraUploads": "相機上載",
"HeaderContinueWatching": "繼續觀看", "HeaderContinueWatching": "繼續觀看",
"HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛的藝人", "HeaderFavoriteArtists": "最愛的藝人",

View File

@ -16,7 +16,6 @@
"Folders": "資料夾", "Folders": "資料夾",
"Genres": "風格", "Genres": "風格",
"HeaderAlbumArtists": "專輯演出者", "HeaderAlbumArtists": "專輯演出者",
"HeaderCameraUploads": "相機上傳",
"HeaderContinueWatching": "繼續觀賞", "HeaderContinueWatching": "繼續觀賞",
"HeaderFavoriteAlbums": "最愛專輯", "HeaderFavoriteAlbums": "最愛專輯",
"HeaderFavoriteArtists": "最愛演出者", "HeaderFavoriteArtists": "最愛演出者",

View File

@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
return new[] return new[]
{ {
// Every so often // Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
}; };
} }

View File

@ -65,7 +65,7 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{ {
return new[] return new[]
{ {
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks} new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks }
}; };
} }

View File

@ -54,7 +54,8 @@ namespace Emby.Server.Implementations.Security
{ {
if (tableNewlyCreated && TableExists(connection, "AccessTokens")) if (tableNewlyCreated && TableExists(connection, "AccessTokens"))
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
var existingColumnNames = GetColumnNames(db, "AccessTokens"); var existingColumnNames = GetColumnNames(db, "AccessTokens");
@ -88,7 +89,8 @@ namespace Emby.Server.Implementations.Security
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)")) using (var statement = db.PrepareStatement("insert into Tokens (AccessToken, DeviceId, AppName, AppVersion, DeviceName, UserId, UserName, IsActive, DateCreated, DateLastActivity) values (@AccessToken, @DeviceId, @AppName, @AppVersion, @DeviceName, @UserId, @UserName, @IsActive, @DateCreated, @DateLastActivity)"))
{ {
@ -119,7 +121,8 @@ namespace Emby.Server.Implementations.Security
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id")) using (var statement = db.PrepareStatement("Update Tokens set AccessToken=@AccessToken, DeviceId=@DeviceId, AppName=@AppName, AppVersion=@AppVersion, DeviceName=@DeviceName, UserId=@UserId, UserName=@UserName, DateCreated=@DateCreated, DateLastActivity=@DateLastActivity where Id=@Id"))
{ {
@ -151,7 +154,8 @@ namespace Emby.Server.Implementations.Security
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id")) using (var statement = db.PrepareStatement("Delete from Tokens where Id=@Id"))
{ {
@ -346,7 +350,8 @@ namespace Emby.Server.Implementations.Security
{ {
using (var connection = GetConnection(true)) using (var connection = GetConnection(true))
{ {
return connection.RunInTransaction(db => return connection.RunInTransaction(
db =>
{ {
using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId")) using (var statement = base.PrepareStatement(db, "select CustomName from Devices where Id=@DeviceId"))
{ {
@ -377,7 +382,8 @@ namespace Emby.Server.Implementations.Security
using (var connection = GetConnection()) using (var connection = GetConnection())
{ {
connection.RunInTransaction(db => connection.RunInTransaction(
db =>
{ {
using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))")) using (var statement = db.PrepareStatement("replace into devices (Id, CustomName, Capabilities) VALUES (@Id, @CustomName, (Select Capabilities from Devices where Id=@Id))"))
{ {

View File

@ -666,7 +666,7 @@ namespace Emby.Server.Implementations.Session
} }
} }
var eventArgs = new PlaybackProgressEventArgs var eventArgs = new PlaybackStartEventArgs
{ {
Item = libraryItem, Item = libraryItem,
Users = users, Users = users,

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Linq; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities; using Jellyfin.Data.Queries;
using MediaBrowser.Model.Activity; using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -39,19 +39,19 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns> /// <returns>A <see cref="QueryResult{ActivityLogEntry}"/> containing the log entries.</returns>
[HttpGet("Entries")] [HttpGet("Entries")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<ActivityLogEntry>> GetLogEntries( public async Task<ActionResult<QueryResult<ActivityLogEntry>>> GetLogEntries(
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] DateTime? minDate, [FromQuery] DateTime? minDate,
[FromQuery] bool? hasUserId) [FromQuery] bool? hasUserId)
{ {
var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>( return await _activityManager.GetPagedResultAsync(new ActivityLogQuery
entries => entries.Where(entry => entry.DateCreated >= minDate {
&& (!hasUserId.HasValue || (hasUserId.Value StartIndex = startIndex,
? entry.UserId != Guid.Empty Limit = limit,
: entry.UserId == Guid.Empty)))); MinDate = minDate,
HasUserId = hasUserId
return _activityManager.GetPagedResult(filterFunc, startIndex, limit); }).ConfigureAwait(false);
} }
} }
} }

View File

@ -54,7 +54,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
}).Where(i => i != null).Select(i => i!.Id).ToArray(); }).Where(i => i != null).Select(i => i!.Id).ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {
@ -263,7 +263,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
@ -298,7 +298,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -397,7 +397,7 @@ namespace Jellyfin.Api.Controllers
}).Where(i => i != null).Select(i => i!.Id).ToArray(); }).Where(i => i != null).Select(i => i!.Id).ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -105,7 +105,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="sortOrder">Optional. Sort Order - Ascending,Descending.</param> /// <param name="sortOrder">Optional. Sort Order - Ascending,Descending.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param> /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <response code="200">Channel items returned.</response> /// <response code="200">Channel items returned.</response>
@ -121,7 +121,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? sortOrder, [FromQuery] string? sortOrder,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] string? sortBy, [FromQuery] string? sortBy,
[FromQuery] string? fields) [FromQuery] string? fields)
{ {
@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
.AddItemFields(fields) .AddItemFields(fields)
}; };
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {
@ -183,7 +183,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="userId">Optional. User Id.</param> /// <param name="userId">Optional. User Id.</param>
/// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param> /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped from the results.</param>
/// <param name="limit">Optional. The maximum number of records to return.</param> /// <param name="limit">Optional. The maximum number of records to return.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="channelIds">Optional. Specify one or more channel id's, comma delimited.</param> /// <param name="channelIds">Optional. Specify one or more channel id's, comma delimited.</param>
/// <response code="200">Latest channel items returned.</response> /// <response code="200">Latest channel items returned.</response>
@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromQuery] int? startIndex, [FromQuery] int? startIndex,
[FromQuery] int? limit, [FromQuery] int? limit,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? channelIds) [FromQuery] string? channelIds)
{ {
@ -217,7 +217,7 @@ namespace Jellyfin.Api.Controllers
.AddItemFields(fields) .AddItemFields(fields)
}; };
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
.ToArray(); .ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -333,7 +333,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param>
/// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param>
/// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param>
/// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> /// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param>
/// <param name="addPlayedIndicator">Optional. Add a played indicator.</param> /// <param name="addPlayedIndicator">Optional. Add a played indicator.</param>
/// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param>
/// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param>
@ -364,7 +364,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? quality, [FromQuery] int? quality,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] bool? cropWhitespace, [FromQuery] bool? cropWhitespace,
[FromQuery] string? format, [FromQuery] ImageFormat? format,
[FromQuery] bool? addPlayedIndicator, [FromQuery] bool? addPlayedIndicator,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
[FromQuery] int? unplayedCount, [FromQuery] int? unplayedCount,
@ -443,7 +443,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? quality, [FromQuery] int? quality,
[FromRoute, Required] string tag, [FromRoute, Required] string tag,
[FromQuery] bool? cropWhitespace, [FromQuery] bool? cropWhitespace,
[FromRoute, Required] string format, [FromRoute, Required] ImageFormat format,
[FromQuery] bool? addPlayedIndicator, [FromQuery] bool? addPlayedIndicator,
[FromRoute, Required] double percentPlayed, [FromRoute, Required] double percentPlayed,
[FromRoute, Required] int unplayedCount, [FromRoute, Required] int unplayedCount,
@ -516,7 +516,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string tag,
[FromQuery] string format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -595,7 +595,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string tag,
[FromQuery] string format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -674,7 +674,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string tag,
[FromQuery] string format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -753,7 +753,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string tag, [FromQuery] string tag,
[FromQuery] string format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -832,7 +832,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] string name, [FromRoute, Required] string name,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute, Required] string tag, [FromRoute, Required] string tag,
[FromRoute, Required] string format, [FromRoute, Required] ImageFormat format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -911,7 +911,7 @@ namespace Jellyfin.Api.Controllers
[FromRoute, Required] Guid userId, [FromRoute, Required] Guid userId,
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] string? tag, [FromQuery] string? tag,
[FromQuery] string? format, [FromQuery] ImageFormat? format,
[FromQuery] int? maxWidth, [FromQuery] int? maxWidth,
[FromQuery] int? maxHeight, [FromQuery] int? maxHeight,
[FromQuery] double? percentPlayed, [FromQuery] double? percentPlayed,
@ -1038,7 +1038,7 @@ namespace Jellyfin.Api.Controllers
ImageType imageType, ImageType imageType,
int? imageIndex, int? imageIndex,
string? tag, string? tag,
string? format, ImageFormat? format,
int? maxWidth, int? maxWidth,
int? maxHeight, int? maxHeight,
double? percentPlayed, double? percentPlayed,
@ -1128,12 +1128,11 @@ namespace Jellyfin.Api.Controllers
isHeadRequest).ConfigureAwait(false); isHeadRequest).ConfigureAwait(false);
} }
private ImageFormat[] GetOutputFormats(string? format) private ImageFormat[] GetOutputFormats(ImageFormat? format)
{ {
if (!string.IsNullOrWhiteSpace(format) if (format.HasValue)
&& Enum.TryParse(format, true, out ImageFormat parsedFormat))
{ {
return new[] { parsedFormat }; return new[] { format.Value };
} }
return GetClientSupportedFormats(); return GetClientSupportedFormats();
@ -1157,7 +1156,7 @@ namespace Jellyfin.Api.Controllers
var acceptParam = Request.Query[HeaderNames.Accept]; var acceptParam = Request.Query[HeaderNames.Accept];
var supportsWebP = SupportsFormat(supportedFormats, acceptParam, "webp", false); var supportsWebP = SupportsFormat(supportedFormats, acceptParam, ImageFormat.Webp, false);
if (!supportsWebP) if (!supportsWebP)
{ {
@ -1179,7 +1178,7 @@ namespace Jellyfin.Api.Controllers
formats.Add(ImageFormat.Jpg); formats.Add(ImageFormat.Jpg);
formats.Add(ImageFormat.Png); formats.Add(ImageFormat.Png);
if (SupportsFormat(supportedFormats, acceptParam, "gif", true)) if (SupportsFormat(supportedFormats, acceptParam, ImageFormat.Gif, true))
{ {
formats.Add(ImageFormat.Gif); formats.Add(ImageFormat.Gif);
} }
@ -1187,9 +1186,10 @@ namespace Jellyfin.Api.Controllers
return formats.ToArray(); return formats.ToArray();
} }
private bool SupportsFormat(IReadOnlyCollection<string> requestAcceptTypes, string acceptParam, string format, bool acceptAll) private bool SupportsFormat(IReadOnlyCollection<string> requestAcceptTypes, string acceptParam, ImageFormat format, bool acceptAll)
{ {
var mimeType = "image/" + format; var normalized = format.ToString().ToLowerInvariant();
var mimeType = "image/" + normalized;
if (requestAcceptTypes.Contains(mimeType)) if (requestAcceptTypes.Contains(mimeType))
{ {
@ -1201,7 +1201,7 @@ namespace Jellyfin.Api.Controllers
return true; return true;
} }
return string.Equals(acceptParam, format, StringComparison.OrdinalIgnoreCase); return string.Equals(acceptParam, normalized, StringComparison.OrdinalIgnoreCase);
} }
private async Task<ActionResult> GetImageResult( private async Task<ActionResult> GetImageResult(

View File

@ -159,7 +159,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isHd, [FromQuery] bool? isHd,
[FromQuery] bool? is4K, [FromQuery] bool? is4K,
[FromQuery] string? locationTypes, [FromQuery] string? locationTypes,
[FromQuery] string? excludeLocationTypes, [FromQuery] LocationType[] excludeLocationTypes,
[FromQuery] bool? isMissing, [FromQuery] bool? isMissing,
[FromQuery] bool? isUnaired, [FromQuery] bool? isUnaired,
[FromQuery] double? minCommunityRating, [FromQuery] double? minCommunityRating,
@ -182,7 +182,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? imageTypes, [FromQuery] string? imageTypes,
@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers
query.CollapseBoxSetItems = false; query.CollapseBoxSetItems = false;
} }
foreach (var filter in RequestHelpers.GetFilters(filters!)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {
@ -406,12 +406,9 @@ namespace Jellyfin.Api.Controllers
} }
// ExcludeLocationTypes // ExcludeLocationTypes
if (!string.IsNullOrEmpty(excludeLocationTypes)) if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
{ {
if (excludeLocationTypes.Split(',').Select(d => (LocationType)Enum.Parse(typeof(LocationType), d, true)).ToArray().Contains(LocationType.Virtual)) query.IsVirtualItem = false;
{
query.IsVirtualItem = false;
}
} }
if (!string.IsNullOrEmpty(locationTypes)) if (!string.IsNullOrEmpty(locationTypes))

View File

@ -55,7 +55,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -187,7 +187,7 @@ namespace Jellyfin.Api.Controllers
.ToArray(); .ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -89,7 +89,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -187,7 +187,7 @@ namespace Jellyfin.Api.Controllers
.ToArray(); .ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -53,7 +53,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param> /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.</param>
/// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param> /// <param name="excludeItemTypes">Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.</param>
/// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param> /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.</param>
/// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.</param> /// <param name="filters">Optional. Specify additional filters to apply.</param>
/// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param> /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
/// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param> /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
/// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param> /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.</param>
@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? includeItemTypes, [FromQuery] string? includeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? genres, [FromQuery] string? genres,
@ -188,7 +188,7 @@ namespace Jellyfin.Api.Controllers
.ToArray(); .ToArray();
} }
foreach (var filter in RequestHelpers.GetFilters(filters)) foreach (var filter in filters)
{ {
switch (filter) switch (filter)
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -124,7 +125,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isHd, [FromQuery] bool? isHd,
[FromQuery] bool? is4K, [FromQuery] bool? is4K,
[FromQuery] string? locationTypes, [FromQuery] string? locationTypes,
[FromQuery] string? excludeLocationTypes, [FromQuery] LocationType[] excludeLocationTypes,
[FromQuery] bool? isMissing, [FromQuery] bool? isMissing,
[FromQuery] bool? isUnaired, [FromQuery] bool? isUnaired,
[FromQuery] double? minCommunityRating, [FromQuery] double? minCommunityRating,
@ -146,7 +147,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] string? parentId, [FromQuery] string? parentId,
[FromQuery] string? fields, [FromQuery] string? fields,
[FromQuery] string? excludeItemTypes, [FromQuery] string? excludeItemTypes,
[FromQuery] string? filters, [FromQuery] ItemFilter[] filters,
[FromQuery] bool? isFavorite, [FromQuery] bool? isFavorite,
[FromQuery] string? mediaTypes, [FromQuery] string? mediaTypes,
[FromQuery] string? imageTypes, [FromQuery] string? imageTypes,

View File

@ -56,18 +56,6 @@ namespace Jellyfin.Api.Helpers
return result; return result;
} }
/// <summary>
/// Get parsed filters.
/// </summary>
/// <param name="filters">The filters.</param>
/// <returns>Item filters.</returns>
public static IEnumerable<ItemFilter> GetFilters(string? filters)
{
return string.IsNullOrEmpty(filters)
? Array.Empty<ItemFilter>()
: filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
}
/// <summary> /// <summary>
/// Splits a string at a separating character into an array of substrings. /// Splits a string at a separating character into an array of substrings.
/// </summary> /// </summary>

Some files were not shown because too many files have changed in this diff Show More