mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-06-03 13:44:22 -04:00
Merge branch 'master' into displaypreferences-efcore
This commit is contained in:
commit
3d69cea1c9
@ -139,3 +139,25 @@ jobs:
|
|||||||
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
|
sudo /srv/repository/collect-server.azure.sh /srv/repository/incoming/azure $(Build.BuildNumber)
|
||||||
rm $0
|
rm $0
|
||||||
exit
|
exit
|
||||||
|
|
||||||
|
- job: PublishNuget
|
||||||
|
displayName: 'Publish NuGet packages'
|
||||||
|
dependsOn:
|
||||||
|
- BuildPackage
|
||||||
|
condition: and(succeeded('BuildPackage'), startsWith(variables['Build.SourceBranch'], 'refs/tags'))
|
||||||
|
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-latest'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- task: NuGetCommand@2
|
||||||
|
inputs:
|
||||||
|
command: 'pack'
|
||||||
|
packagesToPack: Jellyfin.Data/Jellyfin.Data.csproj;MediaBrowser.Common/MediaBrowser.Common.csproj;MediaBrowser.Controller/MediaBrowser.Controller.csproj;MediaBrowser.Model/MediaBrowser.Model.csproj;Emby.Naming/Emby.Naming.csproj
|
||||||
|
packDestination: '$(Build.ArtifactStagingDirectory)'
|
||||||
|
|
||||||
|
- task: NuGetCommand@2
|
||||||
|
inputs:
|
||||||
|
command: 'push'
|
||||||
|
packagesToPush: '$(Build.ArtifactStagingDirectory)/**/*.nupkg'
|
||||||
|
includeNugetOrg: 'true'
|
||||||
|
@ -11,6 +11,7 @@ using MediaBrowser.Controller.Configuration;
|
|||||||
using MediaBrowser.Controller.Dlna;
|
using MediaBrowser.Controller.Dlna;
|
||||||
using MediaBrowser.Controller.Net;
|
using MediaBrowser.Controller.Net;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Emby.Dlna.Api
|
namespace Emby.Dlna.Api
|
||||||
{
|
{
|
||||||
@ -108,7 +109,7 @@ namespace Emby.Dlna.Api
|
|||||||
public string Filename { get; set; }
|
public string Filename { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DlnaServerService : IService, IRequiresRequest
|
public class DlnaServerService : IService
|
||||||
{
|
{
|
||||||
private const string XMLContentType = "text/xml; charset=UTF-8";
|
private const string XMLContentType = "text/xml; charset=UTF-8";
|
||||||
|
|
||||||
@ -127,11 +128,13 @@ namespace Emby.Dlna.Api
|
|||||||
public DlnaServerService(
|
public DlnaServerService(
|
||||||
IDlnaManager dlnaManager,
|
IDlnaManager dlnaManager,
|
||||||
IHttpResultFactory httpResultFactory,
|
IHttpResultFactory httpResultFactory,
|
||||||
IServerConfigurationManager configurationManager)
|
IServerConfigurationManager configurationManager,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
{
|
{
|
||||||
_dlnaManager = dlnaManager;
|
_dlnaManager = dlnaManager;
|
||||||
_resultFactory = httpResultFactory;
|
_resultFactory = httpResultFactory;
|
||||||
_configurationManager = configurationManager;
|
_configurationManager = configurationManager;
|
||||||
|
Request = httpContextAccessor?.HttpContext.GetServiceStackRequest() ?? throw new ArgumentNullException(nameof(httpContextAccessor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetHeader(string name)
|
private string GetHeader(string name)
|
||||||
|
@ -152,11 +152,15 @@ namespace Emby.Dlna.Eventing
|
|||||||
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
|
builder.Append("<e:propertyset xmlns:e=\"urn:schemas-upnp-org:event-1-0\">");
|
||||||
foreach (var key in stateVariables.Keys)
|
foreach (var key in stateVariables.Keys)
|
||||||
{
|
{
|
||||||
builder.Append("<e:property>");
|
builder.Append("<e:property>")
|
||||||
builder.Append("<" + key + ">");
|
.Append('<')
|
||||||
builder.Append(stateVariables[key]);
|
.Append(key)
|
||||||
builder.Append("</" + key + ">");
|
.Append('>')
|
||||||
builder.Append("</e:property>");
|
.Append(stateVariables[key])
|
||||||
|
.Append("</")
|
||||||
|
.Append(key)
|
||||||
|
.Append('>')
|
||||||
|
.Append("</e:property>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</e:propertyset>");
|
builder.Append("</e:propertyset>");
|
||||||
|
@ -4,12 +4,12 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
|
||||||
using Emby.Dlna.Ssdp;
|
using Emby.Dlna.Ssdp;
|
||||||
using MediaBrowser.Common.Net;
|
using MediaBrowser.Common.Net;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -334,7 +334,7 @@ namespace Emby.Dlna.PlayTo
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return DescriptionXmlBuilder.Escape(value);
|
return SecurityElement.Escape(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
|
private Task SetPlay(TransportCommands avCommands, CancellationToken cancellationToken)
|
||||||
|
@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using MediaBrowser.Model.Dlna;
|
using MediaBrowser.Model.Dlna;
|
||||||
@ -64,10 +65,10 @@ namespace Emby.Dlna.Server
|
|||||||
|
|
||||||
foreach (var att in attributes)
|
foreach (var att in attributes)
|
||||||
{
|
{
|
||||||
builder.AppendFormat(" {0}=\"{1}\"", att.Name, att.Value);
|
builder.AppendFormat(CultureInfo.InvariantCulture, " {0}=\"{1}\"", att.Name, att.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append(">");
|
builder.Append('>');
|
||||||
|
|
||||||
builder.Append("<specVersion>");
|
builder.Append("<specVersion>");
|
||||||
builder.Append("<major>1</major>");
|
builder.Append("<major>1</major>");
|
||||||
@ -76,7 +77,9 @@ namespace Emby.Dlna.Server
|
|||||||
|
|
||||||
if (!EnableAbsoluteUrls)
|
if (!EnableAbsoluteUrls)
|
||||||
{
|
{
|
||||||
builder.Append("<URLBase>" + Escape(_serverAddress) + "</URLBase>");
|
builder.Append("<URLBase>")
|
||||||
|
.Append(SecurityElement.Escape(_serverAddress))
|
||||||
|
.Append("</URLBase>");
|
||||||
}
|
}
|
||||||
|
|
||||||
AppendDeviceInfo(builder);
|
AppendDeviceInfo(builder);
|
||||||
@ -93,91 +96,14 @@ namespace Emby.Dlna.Server
|
|||||||
|
|
||||||
AppendIconList(builder);
|
AppendIconList(builder);
|
||||||
|
|
||||||
builder.Append("<presentationURL>" + Escape(_serverAddress) + "/web/index.html</presentationURL>");
|
builder.Append("<presentationURL>")
|
||||||
|
.Append(SecurityElement.Escape(_serverAddress))
|
||||||
|
.Append("/web/index.html</presentationURL>");
|
||||||
|
|
||||||
AppendServiceList(builder);
|
AppendServiceList(builder);
|
||||||
builder.Append("</device>");
|
builder.Append("</device>");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly char[] s_escapeChars = new char[]
|
|
||||||
{
|
|
||||||
'<',
|
|
||||||
'>',
|
|
||||||
'"',
|
|
||||||
'\'',
|
|
||||||
'&'
|
|
||||||
};
|
|
||||||
|
|
||||||
private static readonly string[] s_escapeStringPairs = new[]
|
|
||||||
{
|
|
||||||
"<",
|
|
||||||
"<",
|
|
||||||
">",
|
|
||||||
">",
|
|
||||||
"\"",
|
|
||||||
""",
|
|
||||||
"'",
|
|
||||||
"'",
|
|
||||||
"&",
|
|
||||||
"&"
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string GetEscapeSequence(char c)
|
|
||||||
{
|
|
||||||
int num = s_escapeStringPairs.Length;
|
|
||||||
for (int i = 0; i < num; i += 2)
|
|
||||||
{
|
|
||||||
string text = s_escapeStringPairs[i];
|
|
||||||
string result = s_escapeStringPairs[i + 1];
|
|
||||||
if (text[0] == c)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.ToString(CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>Replaces invalid XML characters in a string with their valid XML equivalent.</summary>
|
|
||||||
/// <returns>The input string with invalid characters replaced.</returns>
|
|
||||||
/// <param name="str">The string within which to escape invalid characters. </param>
|
|
||||||
public static string Escape(string str)
|
|
||||||
{
|
|
||||||
if (str == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringBuilder stringBuilder = null;
|
|
||||||
int length = str.Length;
|
|
||||||
int num = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
int num2 = str.IndexOfAny(s_escapeChars, num);
|
|
||||||
if (num2 == -1)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringBuilder == null)
|
|
||||||
{
|
|
||||||
stringBuilder = new StringBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
stringBuilder.Append(str, num, num2 - num);
|
|
||||||
stringBuilder.Append(GetEscapeSequence(str[num2]));
|
|
||||||
num = num2 + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stringBuilder == null)
|
|
||||||
{
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
stringBuilder.Append(str, num, length - num);
|
|
||||||
return stringBuilder.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AppendDeviceProperties(StringBuilder builder)
|
private void AppendDeviceProperties(StringBuilder builder)
|
||||||
{
|
{
|
||||||
builder.Append("<dlna:X_DLNACAP/>");
|
builder.Append("<dlna:X_DLNACAP/>");
|
||||||
@ -187,32 +113,54 @@ namespace Emby.Dlna.Server
|
|||||||
|
|
||||||
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
builder.Append("<deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>");
|
||||||
|
|
||||||
builder.Append("<friendlyName>" + Escape(GetFriendlyName()) + "</friendlyName>");
|
builder.Append("<friendlyName>")
|
||||||
builder.Append("<manufacturer>" + Escape(_profile.Manufacturer ?? string.Empty) + "</manufacturer>");
|
.Append(SecurityElement.Escape(GetFriendlyName()))
|
||||||
builder.Append("<manufacturerURL>" + Escape(_profile.ManufacturerUrl ?? string.Empty) + "</manufacturerURL>");
|
.Append("</friendlyName>");
|
||||||
|
builder.Append("<manufacturer>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.Manufacturer ?? string.Empty))
|
||||||
|
.Append("</manufacturer>");
|
||||||
|
builder.Append("<manufacturerURL>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ManufacturerUrl ?? string.Empty))
|
||||||
|
.Append("</manufacturerURL>");
|
||||||
|
|
||||||
builder.Append("<modelDescription>" + Escape(_profile.ModelDescription ?? string.Empty) + "</modelDescription>");
|
builder.Append("<modelDescription>")
|
||||||
builder.Append("<modelName>" + Escape(_profile.ModelName ?? string.Empty) + "</modelName>");
|
.Append(SecurityElement.Escape(_profile.ModelDescription ?? string.Empty))
|
||||||
|
.Append("</modelDescription>");
|
||||||
|
builder.Append("<modelName>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ModelName ?? string.Empty))
|
||||||
|
.Append("</modelName>");
|
||||||
|
|
||||||
builder.Append("<modelNumber>" + Escape(_profile.ModelNumber ?? string.Empty) + "</modelNumber>");
|
builder.Append("<modelNumber>")
|
||||||
builder.Append("<modelURL>" + Escape(_profile.ModelUrl ?? string.Empty) + "</modelURL>");
|
.Append(SecurityElement.Escape(_profile.ModelNumber ?? string.Empty))
|
||||||
|
.Append("</modelNumber>");
|
||||||
|
builder.Append("<modelURL>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.ModelUrl ?? string.Empty))
|
||||||
|
.Append("</modelURL>");
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(_profile.SerialNumber))
|
if (string.IsNullOrEmpty(_profile.SerialNumber))
|
||||||
{
|
{
|
||||||
builder.Append("<serialNumber>" + Escape(_serverId) + "</serialNumber>");
|
builder.Append("<serialNumber>")
|
||||||
|
.Append(SecurityElement.Escape(_serverId))
|
||||||
|
.Append("</serialNumber>");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append("<serialNumber>" + Escape(_profile.SerialNumber) + "</serialNumber>");
|
builder.Append("<serialNumber>")
|
||||||
|
.Append(SecurityElement.Escape(_profile.SerialNumber))
|
||||||
|
.Append("</serialNumber>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("<UPC/>");
|
builder.Append("<UPC/>");
|
||||||
|
|
||||||
builder.Append("<UDN>uuid:" + Escape(_serverUdn) + "</UDN>");
|
builder.Append("<UDN>uuid:")
|
||||||
|
.Append(SecurityElement.Escape(_serverUdn))
|
||||||
|
.Append("</UDN>");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
|
if (!string.IsNullOrEmpty(_profile.SonyAggregationFlags))
|
||||||
{
|
{
|
||||||
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">" + Escape(_profile.SonyAggregationFlags) + "</av:aggregationFlags>");
|
builder.Append("<av:aggregationFlags xmlns:av=\"urn:schemas-sony-com:av\">")
|
||||||
|
.Append(SecurityElement.Escape(_profile.SonyAggregationFlags))
|
||||||
|
.Append("</av:aggregationFlags>");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,11 +198,21 @@ namespace Emby.Dlna.Server
|
|||||||
{
|
{
|
||||||
builder.Append("<icon>");
|
builder.Append("<icon>");
|
||||||
|
|
||||||
builder.Append("<mimetype>" + Escape(icon.MimeType ?? string.Empty) + "</mimetype>");
|
builder.Append("<mimetype>")
|
||||||
builder.Append("<width>" + Escape(icon.Width.ToString(_usCulture)) + "</width>");
|
.Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
|
||||||
builder.Append("<height>" + Escape(icon.Height.ToString(_usCulture)) + "</height>");
|
.Append("</mimetype>");
|
||||||
builder.Append("<depth>" + Escape(icon.Depth ?? string.Empty) + "</depth>");
|
builder.Append("<width>")
|
||||||
builder.Append("<url>" + BuildUrl(icon.Url) + "</url>");
|
.Append(SecurityElement.Escape(icon.Width.ToString(_usCulture)))
|
||||||
|
.Append("</width>");
|
||||||
|
builder.Append("<height>")
|
||||||
|
.Append(SecurityElement.Escape(icon.Height.ToString(_usCulture)))
|
||||||
|
.Append("</height>");
|
||||||
|
builder.Append("<depth>")
|
||||||
|
.Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
|
||||||
|
.Append("</depth>");
|
||||||
|
builder.Append("<url>")
|
||||||
|
.Append(BuildUrl(icon.Url))
|
||||||
|
.Append("</url>");
|
||||||
|
|
||||||
builder.Append("</icon>");
|
builder.Append("</icon>");
|
||||||
}
|
}
|
||||||
@ -270,11 +228,21 @@ namespace Emby.Dlna.Server
|
|||||||
{
|
{
|
||||||
builder.Append("<service>");
|
builder.Append("<service>");
|
||||||
|
|
||||||
builder.Append("<serviceType>" + Escape(service.ServiceType ?? string.Empty) + "</serviceType>");
|
builder.Append("<serviceType>")
|
||||||
builder.Append("<serviceId>" + Escape(service.ServiceId ?? string.Empty) + "</serviceId>");
|
.Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
|
||||||
builder.Append("<SCPDURL>" + BuildUrl(service.ScpdUrl) + "</SCPDURL>");
|
.Append("</serviceType>");
|
||||||
builder.Append("<controlURL>" + BuildUrl(service.ControlUrl) + "</controlURL>");
|
builder.Append("<serviceId>")
|
||||||
builder.Append("<eventSubURL>" + BuildUrl(service.EventSubUrl) + "</eventSubURL>");
|
.Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
|
||||||
|
.Append("</serviceId>");
|
||||||
|
builder.Append("<SCPDURL>")
|
||||||
|
.Append(BuildUrl(service.ScpdUrl))
|
||||||
|
.Append("</SCPDURL>");
|
||||||
|
builder.Append("<controlURL>")
|
||||||
|
.Append(BuildUrl(service.ControlUrl))
|
||||||
|
.Append("</controlURL>");
|
||||||
|
builder.Append("<eventSubURL>")
|
||||||
|
.Append(BuildUrl(service.EventSubUrl))
|
||||||
|
.Append("</eventSubURL>");
|
||||||
|
|
||||||
builder.Append("</service>");
|
builder.Append("</service>");
|
||||||
}
|
}
|
||||||
@ -298,7 +266,7 @@ namespace Emby.Dlna.Server
|
|||||||
url = _serverAddress.TrimEnd('/') + url;
|
url = _serverAddress.TrimEnd('/') + url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Escape(url);
|
return SecurityElement.Escape(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<DeviceIcon> GetIcons()
|
private IEnumerable<DeviceIcon> GetIcons()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Security;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Emby.Dlna.Common;
|
using Emby.Dlna.Common;
|
||||||
using Emby.Dlna.Server;
|
|
||||||
|
|
||||||
namespace Emby.Dlna.Service
|
namespace Emby.Dlna.Service
|
||||||
{
|
{
|
||||||
@ -37,7 +37,9 @@ namespace Emby.Dlna.Service
|
|||||||
{
|
{
|
||||||
builder.Append("<action>");
|
builder.Append("<action>");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
|
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||||
|
.Append("</name>");
|
||||||
|
|
||||||
builder.Append("<argumentList>");
|
builder.Append("<argumentList>");
|
||||||
|
|
||||||
@ -45,9 +47,15 @@ namespace Emby.Dlna.Service
|
|||||||
{
|
{
|
||||||
builder.Append("<argument>");
|
builder.Append("<argument>");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(argument.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
builder.Append("<direction>" + DescriptionXmlBuilder.Escape(argument.Direction ?? string.Empty) + "</direction>");
|
.Append(SecurityElement.Escape(argument.Name ?? string.Empty))
|
||||||
builder.Append("<relatedStateVariable>" + DescriptionXmlBuilder.Escape(argument.RelatedStateVariable ?? string.Empty) + "</relatedStateVariable>");
|
.Append("</name>");
|
||||||
|
builder.Append("<direction>")
|
||||||
|
.Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
|
||||||
|
.Append("</direction>");
|
||||||
|
builder.Append("<relatedStateVariable>")
|
||||||
|
.Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
|
||||||
|
.Append("</relatedStateVariable>");
|
||||||
|
|
||||||
builder.Append("</argument>");
|
builder.Append("</argument>");
|
||||||
}
|
}
|
||||||
@ -68,17 +76,25 @@ namespace Emby.Dlna.Service
|
|||||||
{
|
{
|
||||||
var sendEvents = item.SendsEvents ? "yes" : "no";
|
var sendEvents = item.SendsEvents ? "yes" : "no";
|
||||||
|
|
||||||
builder.Append("<stateVariable sendEvents=\"" + sendEvents + "\">");
|
builder.Append("<stateVariable sendEvents=\"")
|
||||||
|
.Append(sendEvents)
|
||||||
|
.Append("\">");
|
||||||
|
|
||||||
builder.Append("<name>" + DescriptionXmlBuilder.Escape(item.Name ?? string.Empty) + "</name>");
|
builder.Append("<name>")
|
||||||
builder.Append("<dataType>" + DescriptionXmlBuilder.Escape(item.DataType ?? string.Empty) + "</dataType>");
|
.Append(SecurityElement.Escape(item.Name ?? string.Empty))
|
||||||
|
.Append("</name>");
|
||||||
|
builder.Append("<dataType>")
|
||||||
|
.Append(SecurityElement.Escape(item.DataType ?? string.Empty))
|
||||||
|
.Append("</dataType>");
|
||||||
|
|
||||||
if (item.AllowedValues.Length > 0)
|
if (item.AllowedValues.Length > 0)
|
||||||
{
|
{
|
||||||
builder.Append("<allowedValueList>");
|
builder.Append("<allowedValueList>");
|
||||||
foreach (var allowedValue in item.AllowedValues)
|
foreach (var allowedValue in item.AllowedValues)
|
||||||
{
|
{
|
||||||
builder.Append("<allowedValue>" + DescriptionXmlBuilder.Escape(allowedValue) + "</allowedValue>");
|
builder.Append("<allowedValue>")
|
||||||
|
.Append(SecurityElement.Escape(allowedValue))
|
||||||
|
.Append("</allowedValue>");
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.Append("</allowedValueList>");
|
builder.Append("</allowedValueList>");
|
||||||
|
@ -136,8 +136,8 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
CleanDateTimes = new[]
|
CleanDateTimes = new[]
|
||||||
{
|
{
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*",
|
@"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
|
||||||
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19\d{2}|20\d{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19\d{2}|20\d{2})*"
|
@"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
|
||||||
};
|
};
|
||||||
|
|
||||||
CleanStrings = new[]
|
CleanStrings = new[]
|
||||||
@ -277,7 +277,7 @@ namespace Emby.Naming.Common
|
|||||||
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
// This isn't a Kodi naming rule, but the expression below causes false positives,
|
||||||
// so we make sure this one gets tested first.
|
// so we make sure this one gets tested first.
|
||||||
// "Foo Bar 889"
|
// "Foo Bar 889"
|
||||||
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/x]*$")
|
new EpisodeExpression(@".*[\\\/](?![Ee]pisode)(?<seriesname>[\w\s]+?)\s(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/x]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
@ -300,32 +300,32 @@ namespace Emby.Naming.Common
|
|||||||
// *** End Kodi Standard Naming
|
// *** End Kodi Standard Naming
|
||||||
|
|
||||||
// [bar] Foo - 1 [baz]
|
// [bar] Foo - 1 [baz]
|
||||||
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>\d+).*$")
|
new EpisodeExpression(@".*?(\[.*?\])+.*?(?<seriesname>[\w\s]+?)[-\s_]+(?<epnumber>[0-9]+).*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>\d+)[xX](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]+)[xX](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>\d+)[x,X]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)[sS](?<seasonnumber>[0-9]+)[x,X]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d+))[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]+))[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d+)[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]+)[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.avi"
|
// "01.avi"
|
||||||
new EpisodeExpression(@".*[\\\/](?<epnumber>\d+)(-(?<endingepnumber>\d+))*\.\w+$")
|
new EpisodeExpression(@".*[\\\/](?<epnumber>[0-9]+)(-(?<endingepnumber>[0-9]+))*\.\w+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
@ -335,34 +335,34 @@ namespace Emby.Naming.Common
|
|||||||
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
new EpisodeExpression(@"([0-9]+)-([0-9]+)"),
|
||||||
|
|
||||||
// "01 - blah.avi", "01-blah.avi"
|
// "01 - blah.avi", "01-blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\s?-\s?[^\\\/]*$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\s?-\s?[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01.blah.avi"
|
// "01.blah.avi"
|
||||||
new EpisodeExpression(@".*(\\|\/)(?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*\.[^\\\/]+$")
|
new EpisodeExpression(@".*(\\|\/)(?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*\.[^\\\/]+$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
|
// "blah - 01.avi", "blah 2 - 01.avi", "blah - 01 blah.avi", "blah 2 - 01 blah", "blah - 01 - blah.avi", "blah 2 - 01 - blah"
|
||||||
new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
new EpisodeExpression(@".*[\\\/][^\\\/]* - (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
|
|
||||||
// "01 episode title.avi"
|
// "01 episode title.avi"
|
||||||
new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>\d{1,3})([^\\\/]*)$")
|
new EpisodeExpression(@"[Ss]eason[\._ ](?<seasonnumber>[0-9]+)[\\\/](?<epnumber>[0-9]{1,3})([^\\\/]*)$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
},
|
},
|
||||||
// "Episode 16", "Episode 16 - Title"
|
// "Episode 16", "Episode 16 - Title"
|
||||||
new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>\d{1,3})(-(?<endingepnumber>\d{2,3}))*[^\\\/]*$")
|
new EpisodeExpression(@".*[\\\/][^\\\/]* (?<epnumber>[0-9]{1,3})(-(?<endingepnumber>[0-9]{2,3}))*[^\\\/]*$")
|
||||||
{
|
{
|
||||||
IsOptimistic = true,
|
IsOptimistic = true,
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
@ -625,17 +625,17 @@ namespace Emby.Naming.Common
|
|||||||
AudioBookPartsExpressions = new[]
|
AudioBookPartsExpressions = new[]
|
||||||
{
|
{
|
||||||
// Detect specified chapters, like CH 01
|
// Detect specified chapters, like CH 01
|
||||||
@"ch(?:apter)?[\s_-]?(?<chapter>\d+)",
|
@"ch(?:apter)?[\s_-]?(?<chapter>[0-9]+)",
|
||||||
// Detect specified parts, like Part 02
|
// Detect specified parts, like Part 02
|
||||||
@"p(?:ar)?t[\s_-]?(?<part>\d+)",
|
@"p(?:ar)?t[\s_-]?(?<part>[0-9]+)",
|
||||||
// Chapter is often beginning of filename
|
// Chapter is often beginning of filename
|
||||||
@"^(?<chapter>\d+)",
|
"^(?<chapter>[0-9]+)",
|
||||||
// Part if often ending of filename
|
// Part if often ending of filename
|
||||||
@"(?<part>\d+)$",
|
"(?<part>[0-9]+)$",
|
||||||
// Sometimes named as 0001_005 (chapter_part)
|
// Sometimes named as 0001_005 (chapter_part)
|
||||||
@"(?<chapter>\d+)_(?<part>\d+)",
|
"(?<chapter>[0-9]+)_(?<part>[0-9]+)",
|
||||||
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
// Some audiobooks are ripped from cd's, and will be named by disk number.
|
||||||
@"dis(?:c|k)[\s_-]?(?<chapter>\d+)"
|
@"dis(?:c|k)[\s_-]?(?<chapter>[0-9]+)"
|
||||||
};
|
};
|
||||||
|
|
||||||
var extensions = VideoFileExtensions.ToList();
|
var extensions = VideoFileExtensions.ToList();
|
||||||
@ -675,16 +675,16 @@ namespace Emby.Naming.Common
|
|||||||
|
|
||||||
MultipleEpisodeExpressions = new string[]
|
MultipleEpisodeExpressions = new string[]
|
||||||
{
|
{
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[eExX](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)[sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3})(-[xE]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )[0-9]{1,4}[xX][eE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>((?![sS]?[0-9]{1,4}[xX][0-9]{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>[0-9]{1,4})[xX](?<epnumber>[0-9]{1,3}))(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$",
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})((-| - )?[xXeE](?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$",
|
||||||
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$"
|
@".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>[0-9]{1,4})[xX\.]?[eE](?<epnumber>[0-9]{1,3})(-[xX]?[eE]?(?<endingepnumber>[0-9]{1,3}))+[^\\\/]*$"
|
||||||
}.Select(i => new EpisodeExpression(i)
|
}.Select(i => new EpisodeExpression(i)
|
||||||
{
|
{
|
||||||
IsNamed = true
|
IsNamed = true
|
||||||
|
@ -192,7 +192,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// Gets or sets the application paths.
|
/// Gets or sets the application paths.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The application paths.</value>
|
/// <value>The application paths.</value>
|
||||||
protected ServerApplicationPaths ApplicationPaths { get; set; }
|
protected IServerApplicationPaths ApplicationPaths { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets all concrete types.
|
/// Gets or sets all concrete types.
|
||||||
@ -236,7 +236,7 @@ namespace Emby.Server.Implementations
|
|||||||
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
/// Initializes a new instance of the <see cref="ApplicationHost" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApplicationHost(
|
public ApplicationHost(
|
||||||
ServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
IStartupOptions options,
|
IStartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
@ -792,7 +792,6 @@ namespace Emby.Server.Implementations
|
|||||||
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
Resolve<IMediaSourceManager>().AddParts(GetExports<IMediaSourceProvider>());
|
||||||
|
|
||||||
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
Resolve<INotificationManager>().AddParts(GetExports<INotificationService>(), GetExports<INotificationTypeFactory>());
|
||||||
Resolve<IUserManager>().AddParts(GetExports<IAuthenticationProvider>(), GetExports<IPasswordResetProvider>());
|
|
||||||
|
|
||||||
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
Resolve<IIsoManager>().AddParts(GetExports<IIsoMounter>());
|
||||||
}
|
}
|
||||||
|
@ -1111,7 +1111,8 @@ namespace Emby.Server.Implementations.Data
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
str.Append(ToValueString(i) + "|");
|
str.Append(ToValueString(i))
|
||||||
|
.Append('|');
|
||||||
}
|
}
|
||||||
|
|
||||||
str.Length -= 1; // Remove last |
|
str.Length -= 1; // Remove last |
|
||||||
@ -2472,7 +2473,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
var item = query.SimilarTo;
|
var item = query.SimilarTo;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("(");
|
builder.Append('(');
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(item.OfficialRating))
|
if (string.IsNullOrEmpty(item.OfficialRating))
|
||||||
{
|
{
|
||||||
@ -2510,7 +2511,7 @@ namespace Emby.Server.Implementations.Data
|
|||||||
if (!string.IsNullOrEmpty(query.SearchTerm))
|
if (!string.IsNullOrEmpty(query.SearchTerm))
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("(");
|
builder.Append('(');
|
||||||
|
|
||||||
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
|
builder.Append("((CleanName like @SearchTermStartsWith or (OriginalTitle not null and OriginalTitle like @SearchTermStartsWith)) * 10)");
|
||||||
|
|
||||||
@ -5239,7 +5240,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
{
|
{
|
||||||
if (i > 0)
|
if (i > 0)
|
||||||
{
|
{
|
||||||
insertText.Append(",");
|
insertText.Append(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture));
|
insertText.AppendFormat("(@ItemId, @AncestorId{0}, @AncestorIdText{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||||
@ -6332,7 +6333,10 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
|||||||
|
|
||||||
foreach (var column in _mediaAttachmentSaveColumns.Skip(1))
|
foreach (var column in _mediaAttachmentSaveColumns.Skip(1))
|
||||||
{
|
{
|
||||||
insertText.Append("@" + column + index + ",");
|
insertText.Append('@')
|
||||||
|
.Append(column)
|
||||||
|
.Append(index)
|
||||||
|
.Append(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
insertText.Length -= 1;
|
insertText.Length -= 1;
|
||||||
|
@ -34,10 +34,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.5" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
<PackageReference Include="Mono.Nat" Version="2.0.1" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="3.3.1" />
|
||||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
|
<PackageReference Include="ServiceStack.Text.Core" Version="5.9.0" />
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Net.Sockets;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Udp;
|
using Emby.Server.Implementations.Udp;
|
||||||
@ -48,8 +49,16 @@ namespace Emby.Server.Implementations.EntryPoints
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task RunAsync()
|
public Task RunAsync()
|
||||||
{
|
{
|
||||||
_udpServer = new UdpServer(_logger, _appHost, _config);
|
try
|
||||||
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
{
|
||||||
|
_udpServer = new UdpServer(_logger, _appHost, _config);
|
||||||
|
_udpServer.Start(PortNumber, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch (SocketException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Unable to start AutoDiscovery listener on UDP port {PortNumber}", PortNumber);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,6 +245,16 @@ namespace Emby.Server.Implementations.IO
|
|||||||
if (info is FileInfo fileInfo)
|
if (info is FileInfo fileInfo)
|
||||||
{
|
{
|
||||||
result.Length = fileInfo.Length;
|
result.Length = fileInfo.Length;
|
||||||
|
|
||||||
|
// Issue #2354 get the size of files behind symbolic links
|
||||||
|
if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
|
||||||
|
{
|
||||||
|
using (Stream thisFileStream = File.OpenRead(fileInfo.FullName))
|
||||||
|
{
|
||||||
|
result.Length = thisFileStream.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
result.DirectoryName = fileInfo.DirectoryName;
|
result.DirectoryName = fileInfo.DirectoryName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,7 +77,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
// Don't resolve these into audio files
|
// Don't resolve these into audio files
|
||||||
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename)
|
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename, StringComparison.Ordinal)
|
||||||
&& _libraryManager.IsAudioFile(filename))
|
&& _libraryManager.IsAudioFile(filename))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
|
@ -11,6 +11,17 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
public class ExclusiveLiveStream : ILiveStream
|
public class ExclusiveLiveStream : ILiveStream
|
||||||
{
|
{
|
||||||
|
private readonly Func<Task> _closeFn;
|
||||||
|
|
||||||
|
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
|
||||||
|
{
|
||||||
|
MediaSource = mediaSource;
|
||||||
|
EnableStreamSharing = false;
|
||||||
|
_closeFn = closeFn;
|
||||||
|
ConsumerCount = 1;
|
||||||
|
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
public int ConsumerCount { get; set; }
|
public int ConsumerCount { get; set; }
|
||||||
|
|
||||||
public string OriginalStreamId { get; set; }
|
public string OriginalStreamId { get; set; }
|
||||||
@ -21,18 +32,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
public MediaSourceInfo MediaSource { get; set; }
|
public MediaSourceInfo MediaSource { get; set; }
|
||||||
|
|
||||||
public string UniqueId { get; private set; }
|
public string UniqueId { get; }
|
||||||
|
|
||||||
private Func<Task> _closeFn;
|
|
||||||
|
|
||||||
public ExclusiveLiveStream(MediaSourceInfo mediaSource, Func<Task> closeFn)
|
|
||||||
{
|
|
||||||
MediaSource = mediaSource;
|
|
||||||
EnableStreamSharing = false;
|
|
||||||
_closeFn = closeFn;
|
|
||||||
ConsumerCount = 1;
|
|
||||||
UniqueId = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task Close()
|
public Task Close()
|
||||||
{
|
{
|
||||||
|
@ -59,6 +59,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class LibraryManager : ILibraryManager
|
public class LibraryManager : ILibraryManager
|
||||||
{
|
{
|
||||||
|
private const string ShortcutFileExtension = ".mblink";
|
||||||
|
|
||||||
private readonly ILogger<LibraryManager> _logger;
|
private readonly ILogger<LibraryManager> _logger;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
@ -74,63 +76,24 @@ namespace Emby.Server.Implementations.Library
|
|||||||
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
private readonly ConcurrentDictionary<Guid, BaseItem> _libraryItemsCache;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The _root folder sync lock.
|
||||||
|
/// </summary>
|
||||||
|
private readonly object _rootFolderSyncLock = new object();
|
||||||
|
private readonly object _userRootFolderSyncLock = new object();
|
||||||
|
|
||||||
|
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
||||||
|
|
||||||
private NamingOptions _namingOptions;
|
private NamingOptions _namingOptions;
|
||||||
private string[] _videoFileExtensions;
|
private string[] _videoFileExtensions;
|
||||||
|
|
||||||
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
|
|
||||||
|
|
||||||
private IProviderManager ProviderManager => _providerManagerFactory.Value;
|
|
||||||
|
|
||||||
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the postscan tasks.
|
/// The _root folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>The postscan tasks.</value>
|
private volatile AggregateFolder _rootFolder;
|
||||||
private ILibraryPostScanTask[] PostscanTasks { get; set; }
|
private volatile UserRootFolder _userRootFolder;
|
||||||
|
|
||||||
/// <summary>
|
private bool _wizardCompleted;
|
||||||
/// Gets or sets the intro providers.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The intro providers.</value>
|
|
||||||
private IIntroProvider[] IntroProviders { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of entity resolution ignore rules.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The entity resolution ignore rules.</value>
|
|
||||||
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the list of currently registered entity resolvers.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The entity resolvers enumerable.</value>
|
|
||||||
private IItemResolver[] EntityResolvers { get; set; }
|
|
||||||
|
|
||||||
private IMultiItemResolver[] MultiItemResolvers { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the comparers.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The comparers.</value>
|
|
||||||
private IBaseItemComparer[] Comparers { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [item added].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ItemChangeEventArgs> ItemAdded;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [item updated].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ItemChangeEventArgs> ItemUpdated;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Occurs when [item removed].
|
|
||||||
/// </summary>
|
|
||||||
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
|
||||||
|
|
||||||
public bool IsScanRunning { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
/// Initializes a new instance of the <see cref="LibraryManager" /> class.
|
||||||
@ -185,37 +148,19 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the parts.
|
/// Occurs when [item added].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rules">The rules.</param>
|
public event EventHandler<ItemChangeEventArgs> ItemAdded;
|
||||||
/// <param name="resolvers">The resolvers.</param>
|
|
||||||
/// <param name="introProviders">The intro providers.</param>
|
|
||||||
/// <param name="itemComparers">The item comparers.</param>
|
|
||||||
/// <param name="postscanTasks">The post scan tasks.</param>
|
|
||||||
public void AddParts(
|
|
||||||
IEnumerable<IResolverIgnoreRule> rules,
|
|
||||||
IEnumerable<IItemResolver> resolvers,
|
|
||||||
IEnumerable<IIntroProvider> introProviders,
|
|
||||||
IEnumerable<IBaseItemComparer> itemComparers,
|
|
||||||
IEnumerable<ILibraryPostScanTask> postscanTasks)
|
|
||||||
{
|
|
||||||
EntityResolutionIgnoreRules = rules.ToArray();
|
|
||||||
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
|
|
||||||
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
|
|
||||||
IntroProviders = introProviders.ToArray();
|
|
||||||
Comparers = itemComparers.ToArray();
|
|
||||||
PostscanTasks = postscanTasks.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder.
|
/// Occurs when [item updated].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private volatile AggregateFolder _rootFolder;
|
public event EventHandler<ItemChangeEventArgs> ItemUpdated;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The _root folder sync lock.
|
/// Occurs when [item removed].
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly object _rootFolderSyncLock = new object();
|
public event EventHandler<ItemChangeEventArgs> ItemRemoved;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the root folder.
|
/// Gets the root folder.
|
||||||
@ -240,7 +185,68 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _wizardCompleted;
|
private ILibraryMonitor LibraryMonitor => _libraryMonitorFactory.Value;
|
||||||
|
|
||||||
|
private IProviderManager ProviderManager => _providerManagerFactory.Value;
|
||||||
|
|
||||||
|
private IUserViewManager UserViewManager => _userviewManagerFactory.Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the postscan tasks.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The postscan tasks.</value>
|
||||||
|
private ILibraryPostScanTask[] PostscanTasks { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the intro providers.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The intro providers.</value>
|
||||||
|
private IIntroProvider[] IntroProviders { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of entity resolution ignore rules.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The entity resolution ignore rules.</value>
|
||||||
|
private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the list of currently registered entity resolvers.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The entity resolvers enumerable.</value>
|
||||||
|
private IItemResolver[] EntityResolvers { get; set; }
|
||||||
|
|
||||||
|
private IMultiItemResolver[] MultiItemResolvers { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the comparers.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The comparers.</value>
|
||||||
|
private IBaseItemComparer[] Comparers { get; set; }
|
||||||
|
|
||||||
|
public bool IsScanRunning { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the parts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rules">The rules.</param>
|
||||||
|
/// <param name="resolvers">The resolvers.</param>
|
||||||
|
/// <param name="introProviders">The intro providers.</param>
|
||||||
|
/// <param name="itemComparers">The item comparers.</param>
|
||||||
|
/// <param name="postscanTasks">The post scan tasks.</param>
|
||||||
|
public void AddParts(
|
||||||
|
IEnumerable<IResolverIgnoreRule> rules,
|
||||||
|
IEnumerable<IItemResolver> resolvers,
|
||||||
|
IEnumerable<IIntroProvider> introProviders,
|
||||||
|
IEnumerable<IBaseItemComparer> itemComparers,
|
||||||
|
IEnumerable<ILibraryPostScanTask> postscanTasks)
|
||||||
|
{
|
||||||
|
EntityResolutionIgnoreRules = rules.ToArray();
|
||||||
|
EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
|
||||||
|
MultiItemResolvers = EntityResolvers.OfType<IMultiItemResolver>().ToArray();
|
||||||
|
IntroProviders = introProviders.ToArray();
|
||||||
|
Comparers = itemComparers.ToArray();
|
||||||
|
PostscanTasks = postscanTasks.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Records the configuration values.
|
/// Records the configuration values.
|
||||||
@ -340,7 +346,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (item is LiveTvProgram)
|
if (item is LiveTvProgram)
|
||||||
{
|
{
|
||||||
_logger.LogDebug(
|
_logger.LogDebug(
|
||||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||||
item.GetType().Name,
|
item.GetType().Name,
|
||||||
item.Name ?? "Unknown name",
|
item.Name ?? "Unknown name",
|
||||||
item.Path ?? string.Empty,
|
item.Path ?? string.Empty,
|
||||||
@ -349,7 +355,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Deleting item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
"Removing item, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||||
item.GetType().Name,
|
item.GetType().Name,
|
||||||
item.Name ?? "Unknown name",
|
item.Name ?? "Unknown name",
|
||||||
item.Path ?? string.Empty,
|
item.Path ?? string.Empty,
|
||||||
@ -367,7 +373,12 @@ namespace Emby.Server.Implementations.Library
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug("Deleting path {MetadataPath}", metadataPath);
|
_logger.LogDebug(
|
||||||
|
"Deleting metadata path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||||
|
item.GetType().Name,
|
||||||
|
item.Name ?? "Unknown name",
|
||||||
|
metadataPath,
|
||||||
|
item.Id);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -391,7 +402,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Deleting path {path}", fileSystemInfo.FullName);
|
_logger.LogInformation(
|
||||||
|
"Deleting item path, Type: {0}, Name: {1}, Path: {2}, Id: {3}",
|
||||||
|
item.GetType().Name,
|
||||||
|
item.Name ?? "Unknown name",
|
||||||
|
fileSystemInfo.FullName,
|
||||||
|
item.Id);
|
||||||
|
|
||||||
if (fileSystemInfo.IsDirectory)
|
if (fileSystemInfo.IsDirectory)
|
||||||
{
|
{
|
||||||
Directory.Delete(fileSystemInfo.FullName, true);
|
Directory.Delete(fileSystemInfo.FullName, true);
|
||||||
@ -500,7 +517,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
// Try to normalize paths located underneath program-data in an attempt to make them more portable
|
// Try to normalize paths located underneath program-data in an attempt to make them more portable
|
||||||
key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
|
key = key.Substring(_configurationManager.ApplicationPaths.ProgramDataPath.Length)
|
||||||
.TrimStart(new[] { '/', '\\' })
|
.TrimStart(new[] { '/', '\\' })
|
||||||
.Replace("/", "\\");
|
.Replace('/', '\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
|
if (forceCaseInsensitive || !_configurationManager.Configuration.EnableCaseSensitiveItemIds)
|
||||||
@ -763,14 +780,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return rootFolder;
|
return rootFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private volatile UserRootFolder _userRootFolder;
|
|
||||||
private readonly object _syncLock = new object();
|
|
||||||
|
|
||||||
public Folder GetUserRootFolder()
|
public Folder GetUserRootFolder()
|
||||||
{
|
{
|
||||||
if (_userRootFolder == null)
|
if (_userRootFolder == null)
|
||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_userRootFolderSyncLock)
|
||||||
{
|
{
|
||||||
if (_userRootFolder == null)
|
if (_userRootFolder == null)
|
||||||
{
|
{
|
||||||
@ -1320,7 +1334,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>
|
||||||
{
|
{
|
||||||
Items = _itemRepository.GetItemList(query).ToArray()
|
Items = _itemRepository.GetItemList(query)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1451,11 +1465,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
return _itemRepository.GetItems(query);
|
return _itemRepository.GetItems(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
var list = _itemRepository.GetItemList(query);
|
|
||||||
|
|
||||||
return new QueryResult<BaseItem>
|
return new QueryResult<BaseItem>
|
||||||
{
|
{
|
||||||
Items = list
|
Items = _itemRepository.GetItemList(query)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1864,7 +1876,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
var outdated = forceUpdate ? item.ImageInfos.Where(i => i.Path != null).ToArray() : item.ImageInfos.Where(ImageNeedsRefresh).ToArray();
|
||||||
if (outdated.Length == 0)
|
// Skip image processing if current or live tv source
|
||||||
|
if (outdated.Length == 0 || item.SourceType != SourceType.Library)
|
||||||
{
|
{
|
||||||
RegisterItem(item);
|
RegisterItem(item);
|
||||||
return;
|
return;
|
||||||
@ -1933,12 +1946,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the item.
|
/// Updates the item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
public void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Don't iterate multiple times
|
foreach (var item in items)
|
||||||
var itemsList = items.ToList();
|
|
||||||
|
|
||||||
foreach (var item in itemsList)
|
|
||||||
{
|
{
|
||||||
if (item.IsFileProtocol)
|
if (item.IsFileProtocol)
|
||||||
{
|
{
|
||||||
@ -1950,11 +1960,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
UpdateImages(item, updateReason >= ItemUpdateType.ImageUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
_itemRepository.SaveItems(itemsList, cancellationToken);
|
_itemRepository.SaveItems(items, cancellationToken);
|
||||||
|
|
||||||
if (ItemUpdated != null)
|
if (ItemUpdated != null)
|
||||||
{
|
{
|
||||||
foreach (var item in itemsList)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
// With the live tv guide this just creates too much noise
|
// With the live tv guide this just creates too much noise
|
||||||
if (item.SourceType != SourceType.Library)
|
if (item.SourceType != SourceType.Library)
|
||||||
@ -2177,8 +2187,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
.FirstOrDefault(i => !string.IsNullOrEmpty(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly TimeSpan _viewRefreshInterval = TimeSpan.FromHours(24);
|
|
||||||
|
|
||||||
public UserView GetNamedView(
|
public UserView GetNamedView(
|
||||||
User user,
|
User user,
|
||||||
string name,
|
string name,
|
||||||
@ -2476,14 +2484,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
var isFolder = episode.VideoType == VideoType.BluRay || episode.VideoType == VideoType.Dvd;
|
||||||
|
|
||||||
var episodeInfo = episode.IsFileProtocol ?
|
var episodeInfo = episode.IsFileProtocol
|
||||||
resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) :
|
? resolver.Resolve(episode.Path, isFolder, null, null, isAbsoluteNaming) ?? new Naming.TV.EpisodeInfo()
|
||||||
new Naming.TV.EpisodeInfo();
|
: new Naming.TV.EpisodeInfo();
|
||||||
|
|
||||||
if (episodeInfo == null)
|
|
||||||
{
|
|
||||||
episodeInfo = new Naming.TV.EpisodeInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -2491,11 +2494,13 @@ namespace Emby.Server.Implementations.Library
|
|||||||
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
|
if (libraryOptions.EnableEmbeddedEpisodeInfos && string.Equals(episodeInfo.Container, "mp4", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// Read from metadata
|
// Read from metadata
|
||||||
var mediaInfo = _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
var mediaInfo = _mediaEncoder.GetMediaInfo(
|
||||||
{
|
new MediaInfoRequest
|
||||||
MediaSource = episode.GetMediaSources(false)[0],
|
{
|
||||||
MediaType = DlnaProfileType.Video
|
MediaSource = episode.GetMediaSources(false)[0],
|
||||||
}, CancellationToken.None).GetAwaiter().GetResult();
|
MediaType = DlnaProfileType.Video
|
||||||
|
},
|
||||||
|
CancellationToken.None).GetAwaiter().GetResult();
|
||||||
if (mediaInfo.ParentIndexNumber > 0)
|
if (mediaInfo.ParentIndexNumber > 0)
|
||||||
{
|
{
|
||||||
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
|
episodeInfo.SeasonNumber = mediaInfo.ParentIndexNumber;
|
||||||
@ -2653,7 +2658,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
var videos = videoListResolver.Resolve(fileSystemChildren);
|
var videos = videoListResolver.Resolve(fileSystemChildren);
|
||||||
|
|
||||||
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files.First().Path, StringComparison.OrdinalIgnoreCase));
|
var currentVideo = videos.FirstOrDefault(i => string.Equals(owner.Path, i.Files[0].Path, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (currentVideo != null)
|
if (currentVideo != null)
|
||||||
{
|
{
|
||||||
@ -2670,9 +2675,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.Select(video =>
|
.Select(video =>
|
||||||
{
|
{
|
||||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||||
var dbItem = GetItemById(video.Id) as Trailer;
|
if (GetItemById(video.Id) is Trailer dbItem)
|
||||||
|
|
||||||
if (dbItem != null)
|
|
||||||
{
|
{
|
||||||
video = dbItem;
|
video = dbItem;
|
||||||
}
|
}
|
||||||
@ -2999,23 +3002,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ValidateNetworkPath(string path)
|
|
||||||
{
|
|
||||||
// if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
|
||||||
//{
|
|
||||||
// // We can't validate protocol-based paths, so just allow them
|
|
||||||
// if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) == -1)
|
|
||||||
// {
|
|
||||||
// return Directory.Exists(path);
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Without native support for unc, we cannot validate this when running under mono
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const string ShortcutFileExtension = ".mblink";
|
|
||||||
|
|
||||||
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
|
public void AddMediaPath(string virtualFolderName, MediaPathInfo pathInfo)
|
||||||
{
|
{
|
||||||
AddMediaPathInternal(virtualFolderName, pathInfo, true);
|
AddMediaPathInternal(virtualFolderName, pathInfo, true);
|
||||||
@ -3040,11 +3026,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new FileNotFoundException("The path does not exist.");
|
throw new FileNotFoundException("The path does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("The network path does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
@ -3083,11 +3064,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
throw new ArgumentNullException(nameof(pathInfo));
|
throw new ArgumentNullException(nameof(pathInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(pathInfo.NetworkPath) && !ValidateNetworkPath(pathInfo.NetworkPath))
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("The network path does not exist.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
var rootFolderPath = _configurationManager.ApplicationPaths.DefaultUserViewsPath;
|
||||||
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
|
||||||
|
|
||||||
@ -3219,7 +3195,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (!Directory.Exists(virtualFolderPath))
|
if (!Directory.Exists(virtualFolderPath))
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
|
throw new FileNotFoundException(
|
||||||
|
string.Format(CultureInfo.InvariantCulture, "The media collection {0} does not exist", virtualFolderName));
|
||||||
}
|
}
|
||||||
|
|
||||||
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
|
var shortcut = _fileSystem.GetFilePaths(virtualFolderPath, true)
|
||||||
|
@ -23,9 +23,8 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
private readonly IMediaEncoder _mediaEncoder;
|
private readonly IMediaEncoder _mediaEncoder;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
private readonly IJsonSerializer _json;
|
||||||
private IJsonSerializer _json;
|
private readonly IApplicationPaths _appPaths;
|
||||||
private IApplicationPaths _appPaths;
|
|
||||||
|
|
||||||
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
|
public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IJsonSerializer json, IApplicationPaths appPaths)
|
||||||
{
|
{
|
||||||
@ -72,13 +71,14 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
mediaSource.AnalyzeDurationMs = 3000;
|
mediaSource.AnalyzeDurationMs = 3000;
|
||||||
|
|
||||||
mediaInfo = await _mediaEncoder.GetMediaInfo(new MediaInfoRequest
|
mediaInfo = await _mediaEncoder.GetMediaInfo(
|
||||||
{
|
new MediaInfoRequest
|
||||||
MediaSource = mediaSource,
|
{
|
||||||
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
MediaSource = mediaSource,
|
||||||
ExtractChapters = false
|
MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
|
||||||
|
ExtractChapters = false
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
},
|
||||||
|
cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (cacheFilePath != null)
|
if (cacheFilePath != null)
|
||||||
{
|
{
|
||||||
@ -126,7 +126,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
mediaSource.RunTimeTicks = null;
|
mediaSource.RunTimeTicks = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Audio);
|
var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||||
|
|
||||||
if (audioStream == null || audioStream.Index == -1)
|
if (audioStream == null || audioStream.Index == -1)
|
||||||
{
|
{
|
||||||
@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
mediaSource.DefaultAudioStreamIndex = audioStream.Index;
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaBrowser.Model.Entities.MediaStreamType.Video);
|
var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||||
if (videoStream != null)
|
if (videoStream != null)
|
||||||
{
|
{
|
||||||
if (!videoStream.BitRate.HasValue)
|
if (!videoStream.BitRate.HasValue)
|
||||||
|
@ -29,6 +29,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
public class MediaSourceManager : IMediaSourceManager, IDisposable
|
public class MediaSourceManager : IMediaSourceManager, IDisposable
|
||||||
{
|
{
|
||||||
|
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||||
|
private const char LiveStreamIdDelimeter = '_';
|
||||||
|
|
||||||
private readonly IItemRepository _itemRepo;
|
private readonly IItemRepository _itemRepo;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
@ -40,6 +43,11 @@ 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 SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
private readonly object _disposeLock = new object();
|
||||||
|
|
||||||
private IMediaSourceProvider[] _providers;
|
private IMediaSourceProvider[] _providers;
|
||||||
|
|
||||||
public MediaSourceManager(
|
public MediaSourceManager(
|
||||||
@ -368,7 +376,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
var preferredSubs = string.IsNullOrEmpty(user.SubtitleLanguagePreference)
|
||||||
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
? Array.Empty<string>() : NormalizeLanguage(user.SubtitleLanguagePreference);
|
||||||
|
|
||||||
@ -451,9 +458,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Dictionary<string, ILiveStream> _openStreams = new Dictionary<string, ILiveStream>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
|
public async Task<Tuple<LiveStreamResponse, IDirectStreamProvider>> OpenLiveStreamInternal(LiveStreamRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
@ -855,9 +859,6 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
|
||||||
private const char LiveStreamIdDelimeter = '_';
|
|
||||||
|
|
||||||
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
|
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(key))
|
if (string.IsNullOrEmpty(key))
|
||||||
@ -881,9 +882,9 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Dispose(true);
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object _disposeLock = new object();
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Releases unmanaged and - optionally - managed resources.
|
/// Releases unmanaged and - optionally - managed resources.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -89,7 +89,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load forced subs if we have found no suitable full subtitles
|
// load forced subs if we have found no suitable full subtitles
|
||||||
stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
|
stream ??= streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
{
|
{
|
||||||
|
@ -20,13 +20,11 @@ namespace Emby.Server.Implementations.Library
|
|||||||
{
|
{
|
||||||
public class SearchEngine : ISearchEngine
|
public class SearchEngine : ISearchEngine
|
||||||
{
|
{
|
||||||
private readonly ILogger<SearchEngine> _logger;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IUserManager _userManager;
|
||||||
|
|
||||||
public SearchEngine(ILogger<SearchEngine> logger, ILibraryManager libraryManager, IUserManager userManager)
|
public SearchEngine(ILibraryManager libraryManager, IUserManager userManager)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_userManager = userManager;
|
_userManager = userManager;
|
||||||
}
|
}
|
||||||
@ -34,11 +32,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
public QueryResult<SearchHintInfo> GetSearchHints(SearchQuery query)
|
||||||
{
|
{
|
||||||
User user = null;
|
User user = null;
|
||||||
|
if (query.UserId != Guid.Empty)
|
||||||
if (query.UserId.Equals(Guid.Empty))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
user = _userManager.GetUserById(query.UserId);
|
user = _userManager.GetUserById(query.UserId);
|
||||||
}
|
}
|
||||||
@ -48,19 +42,19 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (query.StartIndex.HasValue)
|
if (query.StartIndex.HasValue)
|
||||||
{
|
{
|
||||||
results = results.Skip(query.StartIndex.Value).ToList();
|
results = results.GetRange(query.StartIndex.Value, totalRecordCount - query.StartIndex.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.Limit.HasValue)
|
if (query.Limit.HasValue)
|
||||||
{
|
{
|
||||||
results = results.Take(query.Limit.Value).ToList();
|
results = results.GetRange(0, query.Limit.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new QueryResult<SearchHintInfo>
|
return new QueryResult<SearchHintInfo>
|
||||||
{
|
{
|
||||||
TotalRecordCount = totalRecordCount,
|
TotalRecordCount = totalRecordCount,
|
||||||
|
|
||||||
Items = results.ToArray()
|
Items = results
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +79,7 @@ namespace Emby.Server.Implementations.Library
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(searchTerm))
|
if (string.IsNullOrEmpty(searchTerm))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("SearchTerm can't be empty.", nameof(searchTerm));
|
throw new ArgumentException("SearchTerm can't be empty.", nameof(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
searchTerm = searchTerm.Trim().RemoveDiacritics();
|
||||||
|
@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.Library;
|
using Emby.Server.Implementations.Library;
|
||||||
@ -28,7 +29,6 @@ using MediaBrowser.Model.Globalization;
|
|||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.LiveTv;
|
using MediaBrowser.Model.LiveTv;
|
||||||
using MediaBrowser.Model.Querying;
|
using MediaBrowser.Model.Querying;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
|
||||||
@ -54,7 +54,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ITaskManager _taskManager;
|
private readonly ITaskManager _taskManager;
|
||||||
private readonly ILocalizationManager _localization;
|
private readonly ILocalizationManager _localization;
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IChannelManager _channelManager;
|
private readonly IChannelManager _channelManager;
|
||||||
private readonly LiveTvDtoService _tvDtoService;
|
private readonly LiveTvDtoService _tvDtoService;
|
||||||
@ -73,7 +72,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
ILocalizationManager localization,
|
ILocalizationManager localization,
|
||||||
IJsonSerializer jsonSerializer,
|
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IChannelManager channelManager,
|
IChannelManager channelManager,
|
||||||
LiveTvDtoService liveTvDtoService)
|
LiveTvDtoService liveTvDtoService)
|
||||||
@ -85,7 +83,6 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_localization = localization;
|
_localization = localization;
|
||||||
_jsonSerializer = jsonSerializer;
|
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_dtoService = dtoService;
|
_dtoService = dtoService;
|
||||||
_userDataManager = userDataManager;
|
_userDataManager = userDataManager;
|
||||||
@ -2234,7 +2231,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
|
|
||||||
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
public async Task<TunerHostInfo> SaveTunerHost(TunerHostInfo info, bool dataSourceChanged = true)
|
||||||
{
|
{
|
||||||
info = _jsonSerializer.DeserializeFromString<TunerHostInfo>(_jsonSerializer.SerializeToString(info));
|
info = JsonSerializer.Deserialize<TunerHostInfo>(JsonSerializer.Serialize(info));
|
||||||
|
|
||||||
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
var provider = _tunerHosts.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
@ -2278,7 +2275,7 @@ namespace Emby.Server.Implementations.LiveTv
|
|||||||
{
|
{
|
||||||
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
// Hack to make the object a pure ListingsProviderInfo instead of an AddListingProvider
|
||||||
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
|
// ServerConfiguration.SaveConfiguration crashes during xml serialization for AddListingProvider
|
||||||
info = _jsonSerializer.DeserializeFromString<ListingsProviderInfo>(_jsonSerializer.SerializeToString(info));
|
info = JsonSerializer.Deserialize<ListingsProviderInfo>(JsonSerializer.Serialize(info));
|
||||||
|
|
||||||
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
var provider = _listingProviders.FirstOrDefault(i => string.Equals(info.Type, i.Type, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
"AppDeviceValues": "App: {0}, Gerät: {1}",
|
||||||
"Application": "Anwendung",
|
"Application": "Anwendung",
|
||||||
"Artists": "Interpreten",
|
"Artists": "Interpreten",
|
||||||
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich authentifiziert",
|
"AuthenticationSucceededWithUserName": "{0} hat sich erfolgreich angemeldet",
|
||||||
"Books": "Bücher",
|
"Books": "Bücher",
|
||||||
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
"CameraImageUploadedFrom": "Ein neues Foto wurde von {0} hochgeladen",
|
||||||
"Channels": "Kanäle",
|
"Channels": "Kanäle",
|
||||||
|
@ -5,47 +5,47 @@
|
|||||||
"Artists": "Artis",
|
"Artists": "Artis",
|
||||||
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
|
"AuthenticationSucceededWithUserName": "{0} berjaya disahkan",
|
||||||
"Books": "Buku-buku",
|
"Books": "Buku-buku",
|
||||||
"CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
|
"CameraImageUploadedFrom": "Ada gambar dari kamera yang baru dimuat naik melalui {0}",
|
||||||
"Channels": "Saluran",
|
"Channels": "Saluran",
|
||||||
"ChapterNameValue": "Chapter {0}",
|
"ChapterNameValue": "Bab {0}",
|
||||||
"Collections": "Koleksi",
|
"Collections": "Koleksi",
|
||||||
"DeviceOfflineWithName": "{0} has disconnected",
|
"DeviceOfflineWithName": "{0} telah diputuskan sambungan",
|
||||||
"DeviceOnlineWithName": "{0} is connected",
|
"DeviceOnlineWithName": "{0} telah disambung",
|
||||||
"FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
|
"FailedLoginAttemptWithUserName": "Cubaan log masuk gagal dari {0}",
|
||||||
"Favorites": "Favorites",
|
"Favorites": "Kegemaran",
|
||||||
"Folders": "Folders",
|
"Folders": "Fail-fail",
|
||||||
"Genres": "Genre-genre",
|
"Genres": "Genre-genre",
|
||||||
"HeaderAlbumArtists": "Album Artists",
|
"HeaderAlbumArtists": "Album Artis-artis",
|
||||||
"HeaderCameraUploads": "Muatnaik Kamera",
|
"HeaderCameraUploads": "Muatnaik Kamera",
|
||||||
"HeaderContinueWatching": "Terus Menonton",
|
"HeaderContinueWatching": "Terus Menonton",
|
||||||
"HeaderFavoriteAlbums": "Favorite Albums",
|
"HeaderFavoriteAlbums": "Album-album Kegemaran",
|
||||||
"HeaderFavoriteArtists": "Favorite Artists",
|
"HeaderFavoriteArtists": "Artis-artis Kegemaran",
|
||||||
"HeaderFavoriteEpisodes": "Favorite Episodes",
|
"HeaderFavoriteEpisodes": "Episod-episod Kegemaran",
|
||||||
"HeaderFavoriteShows": "Favorite Shows",
|
"HeaderFavoriteShows": "Rancangan-rancangan Kegemaran",
|
||||||
"HeaderFavoriteSongs": "Favorite Songs",
|
"HeaderFavoriteSongs": "Lagu-lagu Kegemaran",
|
||||||
"HeaderLiveTV": "Live TV",
|
"HeaderLiveTV": "TV Siaran Langsung",
|
||||||
"HeaderNextUp": "Next Up",
|
"HeaderNextUp": "Seterusnya",
|
||||||
"HeaderRecordingGroups": "Recording Groups",
|
"HeaderRecordingGroups": "Kumpulan-kumpulan Rakaman",
|
||||||
"HomeVideos": "Home videos",
|
"HomeVideos": "Video Personal",
|
||||||
"Inherit": "Inherit",
|
"Inherit": "Mewarisi",
|
||||||
"ItemAddedWithName": "{0} was added to the library",
|
"ItemAddedWithName": "{0} telah ditambahkan ke dalam pustaka",
|
||||||
"ItemRemovedWithName": "{0} was removed from the library",
|
"ItemRemovedWithName": "{0} telah dibuang daripada pustaka",
|
||||||
"LabelIpAddressValue": "Alamat IP: {0}",
|
"LabelIpAddressValue": "Alamat IP: {0}",
|
||||||
"LabelRunningTimeValue": "Running time: {0}",
|
"LabelRunningTimeValue": "Masa berjalan: {0}",
|
||||||
"Latest": "Latest",
|
"Latest": "Terbaru",
|
||||||
"MessageApplicationUpdated": "Jellyfin Server has been updated",
|
"MessageApplicationUpdated": "Jellyfin Server telah dikemas kini",
|
||||||
"MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
|
"MessageApplicationUpdatedTo": "Jellyfin Server telah dikemas kini ke {0}",
|
||||||
"MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
|
"MessageNamedServerConfigurationUpdatedWithValue": "Konfigurasi pelayan di bahagian {0} telah dikemas kini",
|
||||||
"MessageServerConfigurationUpdated": "Server configuration has been updated",
|
"MessageServerConfigurationUpdated": "Konfigurasi pelayan telah dikemas kini",
|
||||||
"MixedContent": "Mixed content",
|
"MixedContent": "Kandungan campuran",
|
||||||
"Movies": "Movies",
|
"Movies": "Filem",
|
||||||
"Music": "Muzik",
|
"Music": "Muzik",
|
||||||
"MusicVideos": "Video muzik",
|
"MusicVideos": "Video muzik",
|
||||||
"NameInstallFailed": "{0} installation failed",
|
"NameInstallFailed": "{0} pemasangan gagal",
|
||||||
"NameSeasonNumber": "Season {0}",
|
"NameSeasonNumber": "Musim {0}",
|
||||||
"NameSeasonUnknown": "Season Unknown",
|
"NameSeasonUnknown": "Musim Tidak Diketahui",
|
||||||
"NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
|
"NewVersionIsAvailable": "Versi terbaru Jellyfin Server bersedia untuk dimuat turunkan.",
|
||||||
"NotificationOptionApplicationUpdateAvailable": "Application update available",
|
"NotificationOptionApplicationUpdateAvailable": "Kemas kini aplikasi telah sedia",
|
||||||
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
"NotificationOptionApplicationUpdateInstalled": "Application update installed",
|
||||||
"NotificationOptionAudioPlayback": "Audio playback started",
|
"NotificationOptionAudioPlayback": "Audio playback started",
|
||||||
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
"NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
|
||||||
|
@ -67,5 +67,7 @@
|
|||||||
"Artists": "นักแสดง",
|
"Artists": "นักแสดง",
|
||||||
"Application": "แอปพลิเคชั่น",
|
"Application": "แอปพลิเคชั่น",
|
||||||
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
|
"AppDeviceValues": "App: {0}, อุปกรณ์: {1}",
|
||||||
"Albums": "อัลบั้ม"
|
"Albums": "อัลบั้ม",
|
||||||
|
"ScheduledTaskStartedWithName": "{0} เริ่มต้น",
|
||||||
|
"ScheduledTaskFailedWithName": "{0} ล้มเหลว"
|
||||||
}
|
}
|
||||||
|
@ -152,7 +152,12 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] octet = IPAddress.Parse(endpoint).GetAddressBytes();
|
if (!IPAddress.TryParse(endpoint, out var ipAddress))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] octet = ipAddress.GetAddressBytes();
|
||||||
|
|
||||||
if ((octet[0] == 10) ||
|
if ((octet[0] == 10) ||
|
||||||
(octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
|
(octet[0] == 172 && (octet[1] >= 16 && octet[1] <= 31)) || // RFC1918
|
||||||
@ -268,6 +273,12 @@ namespace Emby.Server.Implementations.Networking
|
|||||||
string excludeAddress = "[" + addressString + "]";
|
string excludeAddress = "[" + addressString + "]";
|
||||||
var subnets = LocalSubnetsFn();
|
var subnets = LocalSubnetsFn();
|
||||||
|
|
||||||
|
// Include any address if LAN subnets aren't specified
|
||||||
|
if (subnets.Length == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Exclude any addresses if they appear in the LAN list in [ ]
|
// Exclude any addresses if they appear in the LAN list in [ ]
|
||||||
if (Array.IndexOf(subnets, excludeAddress) != -1)
|
if (Array.IndexOf(subnets, excludeAddress) != -1)
|
||||||
{
|
{
|
||||||
|
@ -189,5 +189,4 @@ namespace Emby.Server.Implementations.Services
|
|||||||
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
return ServiceExecGeneral.Execute(serviceType, req, service, requestDto, requestType.GetMethodName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Reflection;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Emby.Server.Implementations.HttpServer;
|
using Emby.Server.Implementations.HttpServer;
|
||||||
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Model.Services;
|
using MediaBrowser.Model.Services;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -78,7 +79,8 @@ namespace Emby.Server.Implementations.Services
|
|||||||
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
|
var request = await CreateRequest(httpHost, httpReq, _restPath, logger).ConfigureAwait(false);
|
||||||
|
|
||||||
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
|
httpHost.ApplyRequestFilters(httpReq, httpRes, request);
|
||||||
|
|
||||||
|
httpRes.HttpContext.SetServiceStackRequest(httpReq);
|
||||||
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
|
var response = await httpHost.ServiceController.Execute(httpHost, request, httpReq).ConfigureAwait(false);
|
||||||
|
|
||||||
// Apply response filters
|
// Apply response filters
|
||||||
|
@ -488,7 +488,8 @@ namespace Emby.Server.Implementations.Services
|
|||||||
sb.Append(value);
|
sb.Append(value);
|
||||||
for (var j = pathIx + 1; j < requestComponents.Length; j++)
|
for (var j = pathIx + 1; j < requestComponents.Length; j++)
|
||||||
{
|
{
|
||||||
sb.Append(PathSeperatorChar + requestComponents[j]);
|
sb.Append(PathSeperatorChar)
|
||||||
|
.Append(requestComponents[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = sb.ToString();
|
value = sb.ToString();
|
||||||
@ -505,7 +506,8 @@ namespace Emby.Server.Implementations.Services
|
|||||||
pathIx++;
|
pathIx++;
|
||||||
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
|
while (!string.Equals(requestComponents[pathIx], stopLiteral, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
sb.Append(PathSeperatorChar + requestComponents[pathIx++]);
|
sb.Append(PathSeperatorChar)
|
||||||
|
.Append(requestComponents[pathIx++]);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = sb.ToString();
|
value = sb.ToString();
|
||||||
|
@ -117,23 +117,20 @@ namespace Emby.Server.Implementations.TV
|
|||||||
limit = limit.Value + 10;
|
limit = limit.Value + 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
|
var items = _libraryManager
|
||||||
{
|
.GetItemList(
|
||||||
IncludeItemTypes = new[] { typeof(Episode).Name },
|
new InternalItemsQuery(user)
|
||||||
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
|
|
||||||
SeriesPresentationUniqueKey = presentationUniqueKey,
|
|
||||||
Limit = limit,
|
|
||||||
DtoOptions = new DtoOptions
|
|
||||||
{
|
|
||||||
Fields = new ItemFields[]
|
|
||||||
{
|
{
|
||||||
ItemFields.SeriesPresentationUniqueKey
|
IncludeItemTypes = new[] { typeof(Episode).Name },
|
||||||
},
|
OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
|
||||||
EnableImages = false
|
SeriesPresentationUniqueKey = presentationUniqueKey,
|
||||||
},
|
Limit = limit,
|
||||||
GroupBySeriesPresentationUniqueKey = true
|
DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
|
||||||
|
GroupBySeriesPresentationUniqueKey = true
|
||||||
}, parentsFolders.ToList()).Cast<Episode>().Select(GetUniqueSeriesKey);
|
}, parentsFolders.ToList())
|
||||||
|
.Cast<Episode>()
|
||||||
|
.Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
|
||||||
|
.Select(GetUniqueSeriesKey);
|
||||||
|
|
||||||
// Avoid implicitly captured closure
|
// Avoid implicitly captured closure
|
||||||
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
|
var episodes = GetNextUpEpisodes(request, user, items, dtoOptions);
|
||||||
|
@ -148,6 +148,11 @@ namespace Emby.Server.Implementations.Updates
|
|||||||
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
|
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
|
||||||
return Array.Empty<PackageInfo>();
|
return Array.Empty<PackageInfo>();
|
||||||
}
|
}
|
||||||
|
catch (HttpRequestException ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "An error occurred while accessing the plugin manifest: {Manifest}", manifest);
|
||||||
|
return Array.Empty<PackageInfo>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -46,14 +46,12 @@ namespace Jellyfin.Api.Controllers
|
|||||||
[HttpGet("Configuration")]
|
[HttpGet("Configuration")]
|
||||||
public StartupConfigurationDto GetStartupConfiguration()
|
public StartupConfigurationDto GetStartupConfiguration()
|
||||||
{
|
{
|
||||||
var result = new StartupConfigurationDto
|
return new StartupConfigurationDto
|
||||||
{
|
{
|
||||||
UICulture = _config.Configuration.UICulture,
|
UICulture = _config.Configuration.UICulture,
|
||||||
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
|
MetadataCountryCode = _config.Configuration.MetadataCountryCode,
|
||||||
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
|
PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -92,10 +90,10 @@ namespace Jellyfin.Api.Controllers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The first user.</returns>
|
/// <returns>The first user.</returns>
|
||||||
[HttpGet("User")]
|
[HttpGet("User")]
|
||||||
public StartupUserDto GetFirstUser()
|
public async Task<StartupUserDto> GetFirstUser()
|
||||||
{
|
{
|
||||||
// TODO: Remove this method when startup wizard no longer requires an existing user.
|
// TODO: Remove this method when startup wizard no longer requires an existing user.
|
||||||
_userManager.Initialize();
|
await _userManager.InitializeAsync().ConfigureAwait(false);
|
||||||
var user = _userManager.Users.First();
|
var user = _userManager.Users.First();
|
||||||
return new StartupUserDto
|
return new StartupUserDto
|
||||||
{
|
{
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -19,8 +19,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.5" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -24,11 +24,11 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.5">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Jellyfin.Data;
|
using Jellyfin.Data;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
@ -135,6 +136,18 @@ namespace Jellyfin.Server.Implementations
|
|||||||
return base.SaveChanges();
|
return base.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
foreach (var entry in ChangeTracker.Entries())
|
||||||
|
{
|
||||||
|
entry.State = EntityState.Detached;
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
@ -6,6 +6,7 @@ using System.IO;
|
|||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
|
using MediaBrowser.Common;
|
||||||
using MediaBrowser.Common.Extensions;
|
using MediaBrowser.Common.Extensions;
|
||||||
using MediaBrowser.Controller.Authentication;
|
using MediaBrowser.Controller.Authentication;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
@ -23,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
private const string BaseResetFileName = "passwordreset";
|
private const string BaseResetFileName = "passwordreset";
|
||||||
|
|
||||||
private readonly IJsonSerializer _jsonSerializer;
|
private readonly IJsonSerializer _jsonSerializer;
|
||||||
private readonly IUserManager _userManager;
|
private readonly IApplicationHost _appHost;
|
||||||
|
|
||||||
private readonly string _passwordResetFileBase;
|
private readonly string _passwordResetFileBase;
|
||||||
private readonly string _passwordResetFileBaseDir;
|
private readonly string _passwordResetFileBaseDir;
|
||||||
@ -33,16 +34,17 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurationManager">The configuration manager.</param>
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
/// <param name="jsonSerializer">The JSON serializer.</param>
|
/// <param name="jsonSerializer">The JSON serializer.</param>
|
||||||
/// <param name="userManager">The user manager.</param>
|
/// <param name="appHost">The application host.</param>
|
||||||
public DefaultPasswordResetProvider(
|
public DefaultPasswordResetProvider(
|
||||||
IServerConfigurationManager configurationManager,
|
IServerConfigurationManager configurationManager,
|
||||||
IJsonSerializer jsonSerializer,
|
IJsonSerializer jsonSerializer,
|
||||||
IUserManager userManager)
|
IApplicationHost appHost)
|
||||||
{
|
{
|
||||||
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
_passwordResetFileBaseDir = configurationManager.ApplicationPaths.ProgramDataPath;
|
||||||
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
|
_passwordResetFileBase = Path.Combine(_passwordResetFileBaseDir, BaseResetFileName);
|
||||||
_jsonSerializer = jsonSerializer;
|
_jsonSerializer = jsonSerializer;
|
||||||
_userManager = userManager;
|
_appHost = appHost;
|
||||||
|
// TODO: Remove the circular dependency on UserManager
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@ -54,6 +56,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
public async Task<PinRedeemResult> RedeemPasswordResetPin(string pin)
|
||||||
{
|
{
|
||||||
|
var userManager = _appHost.Resolve<IUserManager>();
|
||||||
var usersReset = new List<string>();
|
var usersReset = new List<string>();
|
||||||
foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
|
foreach (var resetFile in Directory.EnumerateFiles(_passwordResetFileBaseDir, $"{BaseResetFileName}*"))
|
||||||
{
|
{
|
||||||
@ -72,10 +75,10 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
pin.Replace("-", string.Empty, StringComparison.Ordinal),
|
||||||
StringComparison.InvariantCultureIgnoreCase))
|
StringComparison.InvariantCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
var resetUser = _userManager.GetUserByName(spr.UserName)
|
var resetUser = userManager.GetUserByName(spr.UserName)
|
||||||
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
|
?? throw new ResourceNotFoundException($"User with a username of {spr.UserName} not found");
|
||||||
|
|
||||||
await _userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
await userManager.ChangePassword(resetUser, pin).ConfigureAwait(false);
|
||||||
usersReset.Add(resetUser.Username);
|
usersReset.Add(resetUser.Username);
|
||||||
File.Delete(resetFile);
|
File.Delete(resetFile);
|
||||||
}
|
}
|
||||||
@ -121,7 +124,6 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
user.EasyPassword = pin;
|
user.EasyPassword = pin;
|
||||||
await _userManager.UpdateUserAsync(user).ConfigureAwait(false);
|
|
||||||
|
|
||||||
return new ForgotPasswordResult
|
return new ForgotPasswordResult
|
||||||
{
|
{
|
||||||
|
@ -39,12 +39,11 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
private readonly IApplicationHost _appHost;
|
private readonly IApplicationHost _appHost;
|
||||||
private readonly IImageProcessor _imageProcessor;
|
private readonly IImageProcessor _imageProcessor;
|
||||||
private readonly ILogger<UserManager> _logger;
|
private readonly ILogger<UserManager> _logger;
|
||||||
|
private readonly IReadOnlyCollection<IPasswordResetProvider> _passwordResetProviders;
|
||||||
private IAuthenticationProvider[] _authenticationProviders = null!;
|
private readonly IReadOnlyCollection<IAuthenticationProvider> _authenticationProviders;
|
||||||
private DefaultAuthenticationProvider _defaultAuthenticationProvider = null!;
|
private readonly InvalidAuthProvider _invalidAuthProvider;
|
||||||
private InvalidAuthProvider _invalidAuthProvider = null!;
|
private readonly DefaultAuthenticationProvider _defaultAuthenticationProvider;
|
||||||
private IPasswordResetProvider[] _passwordResetProviders = null!;
|
private readonly DefaultPasswordResetProvider _defaultPasswordResetProvider;
|
||||||
private DefaultPasswordResetProvider _defaultPasswordResetProvider = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserManager"/> class.
|
/// Initializes a new instance of the <see cref="UserManager"/> class.
|
||||||
@ -69,6 +68,13 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
_appHost = appHost;
|
_appHost = appHost;
|
||||||
_imageProcessor = imageProcessor;
|
_imageProcessor = imageProcessor;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
|
_passwordResetProviders = appHost.GetExports<IPasswordResetProvider>();
|
||||||
|
_authenticationProviders = appHost.GetExports<IAuthenticationProvider>();
|
||||||
|
|
||||||
|
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
||||||
|
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
||||||
|
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -102,7 +108,16 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<Guid> UsersIds => _dbProvider.CreateContext().Users.Select(u => u.Id);
|
public IEnumerable<Guid> UsersIds
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
using var dbContext = _dbProvider.CreateContext();
|
||||||
|
return dbContext.Users
|
||||||
|
.Select(user => user.Id)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public User? GetUserById(Guid id)
|
public User? GetUserById(Guid id)
|
||||||
@ -152,12 +167,12 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
throw new ArgumentException("Invalid username", nameof(newName));
|
throw new ArgumentException("Invalid username", nameof(newName));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.Username.Equals(newName, StringComparison.OrdinalIgnoreCase))
|
if (user.Username.Equals(newName, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
throw new ArgumentException("The new and old names must be different.");
|
throw new ArgumentException("The new and old names must be different.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.OrdinalIgnoreCase)))
|
if (Users.Any(u => u.Id != user.Id && u.Username.Equals(newName, StringComparison.Ordinal)))
|
||||||
{
|
{
|
||||||
throw new ArgumentException(string.Format(
|
throw new ArgumentException(string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
@ -188,8 +203,24 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<User> CreateUserInternalAsync(string name, JellyfinDb dbContext)
|
||||||
|
{
|
||||||
|
// TODO: Remove after user item data is migrated.
|
||||||
|
var max = await dbContext.Users.AnyAsync().ConfigureAwait(false)
|
||||||
|
? await dbContext.Users.Select(u => u.InternalId).MaxAsync().ConfigureAwait(false)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
return new User(
|
||||||
|
name,
|
||||||
|
_defaultAuthenticationProvider.GetType().FullName,
|
||||||
|
_defaultPasswordResetProvider.GetType().FullName)
|
||||||
|
{
|
||||||
|
InternalId = max + 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public User CreateUser(string name)
|
public async Task<User> CreateUserAsync(string name)
|
||||||
{
|
{
|
||||||
if (!IsValidUsername(name))
|
if (!IsValidUsername(name))
|
||||||
{
|
{
|
||||||
@ -198,18 +229,10 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
|
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
using var dbContext = _dbProvider.CreateContext();
|
||||||
|
|
||||||
// TODO: Remove after user item data is migrated.
|
var newUser = await CreateUserInternalAsync(name, dbContext).ConfigureAwait(false);
|
||||||
var max = dbContext.Users.Any() ? dbContext.Users.Select(u => u.InternalId).Max() : 0;
|
|
||||||
|
|
||||||
var newUser = new User(
|
|
||||||
name,
|
|
||||||
_defaultAuthenticationProvider.GetType().FullName,
|
|
||||||
_defaultPasswordResetProvider.GetType().FullName)
|
|
||||||
{
|
|
||||||
InternalId = max + 1
|
|
||||||
};
|
|
||||||
dbContext.Users.Add(newUser);
|
dbContext.Users.Add(newUser);
|
||||||
dbContext.SaveChanges();
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser));
|
OnUserCreated?.Invoke(this, new GenericEventArgs<User>(newUser));
|
||||||
|
|
||||||
@ -512,7 +535,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IncrementInvalidLoginAttemptCount(user);
|
await IncrementInvalidLoginAttemptCount(user).ConfigureAwait(false);
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Authentication request for {UserName} has been denied (IP: {IP}).",
|
"Authentication request for {UserName} has been denied (IP: {IP}).",
|
||||||
user.Username,
|
user.Username,
|
||||||
@ -530,7 +553,12 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
if (user != null && isInNetwork)
|
if (user != null && isInNetwork)
|
||||||
{
|
{
|
||||||
var passwordResetProvider = GetPasswordResetProvider(user);
|
var passwordResetProvider = GetPasswordResetProvider(user);
|
||||||
return await passwordResetProvider.StartForgotPasswordProcess(user, isInNetwork).ConfigureAwait(false);
|
var result = await passwordResetProvider
|
||||||
|
.StartForgotPasswordProcess(user, isInNetwork)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ForgotPasswordResult
|
return new ForgotPasswordResult
|
||||||
@ -560,24 +588,13 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders)
|
|
||||||
{
|
|
||||||
_authenticationProviders = authenticationProviders.ToArray();
|
|
||||||
_passwordResetProviders = passwordResetProviders.ToArray();
|
|
||||||
|
|
||||||
_invalidAuthProvider = _authenticationProviders.OfType<InvalidAuthProvider>().First();
|
|
||||||
_defaultAuthenticationProvider = _authenticationProviders.OfType<DefaultAuthenticationProvider>().First();
|
|
||||||
_defaultPasswordResetProvider = _passwordResetProviders.OfType<DefaultPasswordResetProvider>().First();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void Initialize()
|
public async Task InitializeAsync()
|
||||||
{
|
{
|
||||||
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
|
// TODO: Refactor the startup wizard so that it doesn't require a user to already exist.
|
||||||
using var dbContext = _dbProvider.CreateContext();
|
using var dbContext = _dbProvider.CreateContext();
|
||||||
|
|
||||||
if (dbContext.Users.Any())
|
if (await dbContext.Users.AnyAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -595,13 +612,13 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
throw new ArgumentException("Provided username is not valid!", defaultName);
|
throw new ArgumentException("Provided username is not valid!", defaultName);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newUser = CreateUser(defaultName);
|
var newUser = await CreateUserInternalAsync(defaultName, dbContext).ConfigureAwait(false);
|
||||||
newUser.SetPermission(PermissionKind.IsAdministrator, true);
|
newUser.SetPermission(PermissionKind.IsAdministrator, true);
|
||||||
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
|
newUser.SetPermission(PermissionKind.EnableContentDeletion, true);
|
||||||
newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
|
newUser.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, true);
|
||||||
|
|
||||||
dbContext.Users.Update(newUser);
|
dbContext.Users.Add(newUser);
|
||||||
dbContext.SaveChanges();
|
await dbContext.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -637,7 +654,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdateConfiguration(Guid userId, UserConfiguration config)
|
public void UpdateConfiguration(Guid userId, UserConfiguration config)
|
||||||
{
|
{
|
||||||
var dbContext = _dbProvider.CreateContext();
|
using var dbContext = _dbProvider.CreateContext();
|
||||||
var user = dbContext.Users
|
var user = dbContext.Users
|
||||||
.Include(u => u.Permissions)
|
.Include(u => u.Permissions)
|
||||||
.Include(u => u.Preferences)
|
.Include(u => u.Preferences)
|
||||||
@ -670,8 +687,14 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void UpdatePolicy(Guid userId, UserPolicy policy)
|
public void UpdatePolicy(Guid userId, UserPolicy policy)
|
||||||
{
|
{
|
||||||
var dbContext = _dbProvider.CreateContext();
|
using var dbContext = _dbProvider.CreateContext();
|
||||||
var user = dbContext.Users.Find(userId) ?? throw new ArgumentException("No user exists with given Id!");
|
var user = dbContext.Users
|
||||||
|
.Include(u => u.Permissions)
|
||||||
|
.Include(u => u.Preferences)
|
||||||
|
.Include(u => u.AccessSchedules)
|
||||||
|
.Include(u => u.ProfileImage)
|
||||||
|
.FirstOrDefault(u => u.Id == userId)
|
||||||
|
?? throw new ArgumentException("No user exists with given Id!");
|
||||||
|
|
||||||
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
|
// The default number of login attempts is 3, but for some god forsaken reason it's sent to the server as "0"
|
||||||
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
|
int? maxLoginAttempts = policy.LoginAttemptsBeforeLockout switch
|
||||||
@ -876,7 +899,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IncrementInvalidLoginAttemptCount(User user)
|
private async Task IncrementInvalidLoginAttemptCount(User user)
|
||||||
{
|
{
|
||||||
user.InvalidLoginAttemptCount++;
|
user.InvalidLoginAttemptCount++;
|
||||||
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
|
int? maxInvalidLogins = user.LoginAttemptsBeforeLockout;
|
||||||
@ -890,7 +913,7 @@ namespace Jellyfin.Server.Implementations.Users
|
|||||||
user.InvalidLoginAttemptCount);
|
user.InvalidLoginAttemptCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateUser(user);
|
await UpdateUserAsync(user).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ namespace Jellyfin.Server
|
|||||||
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="fileSystem">The <see cref="IFileSystem" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
|
/// <param name="networkManager">The <see cref="INetworkManager" /> to be used by the <see cref="CoreAppHost" />.</param>
|
||||||
public CoreAppHost(
|
public CoreAppHost(
|
||||||
ServerApplicationPaths applicationPaths,
|
IServerApplicationPaths applicationPaths,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
StartupOptions options,
|
IStartupOptions options,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
INetworkManager networkManager)
|
INetworkManager networkManager)
|
||||||
: base(
|
: base(
|
||||||
|
@ -41,8 +41,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.6" />
|
||||||
<PackageReference Include="prometheus-net" Version="3.6.0" />
|
<PackageReference Include="prometheus-net" Version="3.6.0" />
|
||||||
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
|
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||||
|
@ -17,6 +17,11 @@ namespace Jellyfin.Server.Migrations
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether to perform migration on a new install.
|
||||||
|
/// </summary>
|
||||||
|
public bool PerformOnNewInstall { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Execute the migration routine.
|
/// Execute the migration routine.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -22,6 +22,7 @@ namespace Jellyfin.Server.Migrations
|
|||||||
typeof(Routines.RemoveDuplicateExtras),
|
typeof(Routines.RemoveDuplicateExtras),
|
||||||
typeof(Routines.AddDefaultPluginRepository),
|
typeof(Routines.AddDefaultPluginRepository),
|
||||||
typeof(Routines.MigrateUserDb),
|
typeof(Routines.MigrateUserDb),
|
||||||
|
typeof(Routines.ReaddDefaultPluginRepository),
|
||||||
typeof(Routines.MigrateDisplayPreferencesDb)
|
typeof(Routines.MigrateDisplayPreferencesDb)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,9 +45,8 @@ namespace Jellyfin.Server.Migrations
|
|||||||
// If startup wizard is not finished, this is a fresh install.
|
// If startup wizard is not finished, this is a fresh install.
|
||||||
// Don't run any migrations, just mark all of them as applied.
|
// Don't run any migrations, just mark all of them as applied.
|
||||||
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
|
logger.LogInformation("Marking all known migrations as applied because this is a fresh install");
|
||||||
migrationOptions.Applied.AddRange(migrations.Select(m => (m.Id, m.Name)));
|
migrationOptions.Applied.AddRange(migrations.Where(m => !m.PerformOnNewInstall).Select(m => (m.Id, m.Name)));
|
||||||
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
host.ServerConfigurationManager.SaveConfiguration(MigrationsListStore.StoreKey, migrationOptions);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
|
var appliedMigrationIds = migrationOptions.Applied.Select(m => m.Id).ToHashSet();
|
||||||
|
@ -32,6 +32,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "AddDefaultPluginRepository";
|
public string Name => "AddDefaultPluginRepository";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => true;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -48,6 +48,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "CreateLoggingConfigHeirarchy";
|
public string Name => "CreateLoggingConfigHeirarchy";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -25,6 +25,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "DisableTranscodingThrottling";
|
public string Name => "DisableTranscodingThrottling";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -41,6 +41,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "MigrateActivityLogDatabase";
|
public string Name => "MigrateActivityLogDatabase";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -54,6 +54,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "MigrateUserDatabase";
|
public string Name => "MigrateUserDatabase";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Model.Updates;
|
||||||
|
|
||||||
|
namespace Jellyfin.Server.Migrations.Routines
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Migration to initialize system configuration with the default plugin repository.
|
||||||
|
/// </summary>
|
||||||
|
public class ReaddDefaultPluginRepository : IMigrationRoutine
|
||||||
|
{
|
||||||
|
private readonly IServerConfigurationManager _serverConfigurationManager;
|
||||||
|
|
||||||
|
private readonly RepositoryInfo _defaultRepositoryInfo = new RepositoryInfo
|
||||||
|
{
|
||||||
|
Name = "Jellyfin Stable",
|
||||||
|
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json"
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="ReaddDefaultPluginRepository"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
|
||||||
|
public ReaddDefaultPluginRepository(IServerConfigurationManager serverConfigurationManager)
|
||||||
|
{
|
||||||
|
_serverConfigurationManager = serverConfigurationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public Guid Id => Guid.Parse("5F86E7F6-D966-4C77-849D-7A7B40B68C4E");
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public string Name => "ReaddDefaultPluginRepository";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => true;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Perform()
|
||||||
|
{
|
||||||
|
// Only add if repository list is empty
|
||||||
|
if (_serverConfigurationManager.Configuration.PluginRepositories.Count == 0)
|
||||||
|
{
|
||||||
|
_serverConfigurationManager.Configuration.PluginRepositories.Add(_defaultRepositoryInfo);
|
||||||
|
_serverConfigurationManager.SaveConfiguration();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,9 @@ namespace Jellyfin.Server.Migrations.Routines
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => "RemoveDuplicateExtras";
|
public string Name => "RemoveDuplicateExtras";
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool PerformOnNewInstall => false;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Perform()
|
public void Perform()
|
||||||
{
|
{
|
||||||
|
@ -343,6 +343,21 @@ namespace Jellyfin.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind to unix socket (only on OSX and Linux)
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// TODO: allow configuration of socket path
|
||||||
|
var socketPath = $"{appPaths.DataPath}/socket.sock";
|
||||||
|
// Workaround for https://github.com/aspnet/AspNetCore/issues/14134
|
||||||
|
if (File.Exists(socketPath))
|
||||||
|
{
|
||||||
|
File.Delete(socketPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.ListenUnixSocket(socketPath);
|
||||||
|
_logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
|
.ConfigureAppConfiguration(config => config.ConfigureAppConfiguration(commandLineOpts, appPaths, startupConfig))
|
||||||
.UseSerilog()
|
.UseSerilog()
|
||||||
|
@ -895,6 +895,11 @@ namespace MediaBrowser.Api.Images
|
|||||||
// Handle image/png; charset=utf-8
|
// Handle image/png; charset=utf-8
|
||||||
mimeType = mimeType.Split(';').FirstOrDefault();
|
mimeType = mimeType.Split(';').FirstOrDefault();
|
||||||
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
|
var userDataPath = Path.Combine(ServerConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username);
|
||||||
|
if (user.ProfileImage != null)
|
||||||
|
{
|
||||||
|
_userManager.ClearProfileImage(user);
|
||||||
|
}
|
||||||
|
|
||||||
user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
|
user.ProfileImage = new Jellyfin.Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType)));
|
||||||
|
|
||||||
await _providerManager
|
await _providerManager
|
||||||
|
@ -56,7 +56,10 @@ namespace MediaBrowser.Api.System
|
|||||||
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
|
||||||
|
|
||||||
var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
|
var filterFunc = new Func<IQueryable<ActivityLog>, IQueryable<ActivityLog>>(
|
||||||
entries => entries.Where(entry => entry.DateCreated >= minDate));
|
entries => entries.Where(entry => entry.DateCreated >= minDate
|
||||||
|
&& (!request.HasUserId.HasValue || (request.HasUserId.Value
|
||||||
|
? entry.UserId != Guid.Empty
|
||||||
|
: entry.UserId == Guid.Empty))));
|
||||||
|
|
||||||
var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit);
|
var result = _activityManager.GetPagedResult(filterFunc, request.StartIndex, request.Limit);
|
||||||
|
|
||||||
|
@ -525,7 +525,7 @@ namespace MediaBrowser.Api
|
|||||||
/// <returns>System.Object.</returns>
|
/// <returns>System.Object.</returns>
|
||||||
public async Task<object> Post(CreateUserByName request)
|
public async Task<object> Post(CreateUserByName request)
|
||||||
{
|
{
|
||||||
var newUser = _userManager.CreateUser(request.Name);
|
var newUser = await _userManager.CreateUserAsync(request.Name).ConfigureAwait(false);
|
||||||
|
|
||||||
// no need to authenticate password for new user
|
// no need to authenticate password for new user
|
||||||
if (request.Password != null)
|
if (request.Password != null)
|
||||||
|
33
MediaBrowser.Common/Extensions/HttpContextExtensions.cs
Normal file
33
MediaBrowser.Common/Extensions/HttpContextExtensions.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using MediaBrowser.Model.Services;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace MediaBrowser.Common.Extensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Static class containing extension methods for <see cref="HttpContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static class HttpContextExtensions
|
||||||
|
{
|
||||||
|
private const string ServiceStackRequest = "ServiceStackRequest";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the ServiceStack request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The HttpContext instance.</param>
|
||||||
|
/// <param name="request">The service stack request instance.</param>
|
||||||
|
public static void SetServiceStackRequest(this HttpContext httpContext, IRequest request)
|
||||||
|
{
|
||||||
|
httpContext.Items[ServiceStackRequest] = request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the ServiceStack request.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="httpContext">The HttpContext instance.</param>
|
||||||
|
/// <returns>The service stack request instance.</returns>
|
||||||
|
public static IRequest GetServiceStackRequest(this HttpContext httpContext)
|
||||||
|
{
|
||||||
|
return (IRequest)httpContext.Items[ServiceStackRequest];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,8 +17,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
<PackageReference Include="Microsoft.Net.Http.Headers" Version="2.2.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -200,7 +200,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the item.
|
/// Updates the item.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void UpdateItems(IEnumerable<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
|
void UpdateItems(IReadOnlyList<BaseItem> items, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
|
||||||
|
|
||||||
void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
|
void UpdateItem(BaseItem item, BaseItem parent, ItemUpdateType updateReason, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Data.Entities;
|
using Jellyfin.Data.Entities;
|
||||||
using MediaBrowser.Controller.Authentication;
|
|
||||||
using MediaBrowser.Model.Configuration;
|
using MediaBrowser.Model.Configuration;
|
||||||
using MediaBrowser.Model.Dto;
|
using MediaBrowser.Model.Dto;
|
||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
@ -55,7 +54,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the user manager and ensures that a user exists.
|
/// Initializes the user manager and ensures that a user exists.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Initialize();
|
Task InitializeAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a user by Id.
|
/// Gets a user by Id.
|
||||||
@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <returns>The created user.</returns>
|
/// <returns>The created user.</returns>
|
||||||
/// <exception cref="ArgumentNullException">name</exception>
|
/// <exception cref="ArgumentNullException">name</exception>
|
||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
User CreateUser(string name);
|
Task<User> CreateUserAsync(string name);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deletes the specified user.
|
/// Deletes the specified user.
|
||||||
@ -166,8 +165,6 @@ namespace MediaBrowser.Controller.Library
|
|||||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
|
||||||
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
|
Task<PinRedeemResult> RedeemPasswordResetPin(string pin);
|
||||||
|
|
||||||
void AddParts(IEnumerable<IAuthenticationProvider> authenticationProviders, IEnumerable<IPasswordResetProvider> passwordResetProviders);
|
|
||||||
|
|
||||||
NameIdPair[] GetAuthenticationProviders();
|
NameIdPair[] GetAuthenticationProviders();
|
||||||
|
|
||||||
NameIdPair[] GetPasswordResetProviders();
|
NameIdPair[] GetPasswordResetProviders();
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="3.1.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Jellyfin.Data.Enums;
|
using Jellyfin.Data.Enums;
|
||||||
@ -371,7 +372,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
public int GetVideoProfileScore(string profile)
|
public int GetVideoProfileScore(string profile)
|
||||||
{
|
{
|
||||||
// strip spaces because they may be stripped out on the query string
|
// strip spaces because they may be stripped out on the query string
|
||||||
profile = profile.Replace(" ", "");
|
profile = profile.Replace(" ", string.Empty, StringComparison.Ordinal);
|
||||||
return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
|
return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -449,41 +450,59 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var arg = new StringBuilder();
|
var arg = new StringBuilder();
|
||||||
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
|
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
|
||||||
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
|
var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
|
||||||
bool isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
bool isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
bool isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
bool isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (!IsCopyCodec(outputVideoCodec))
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
if (isVaapiDecoder)
|
if (state.IsVideoRequest
|
||||||
|
&& _mediaEncoder.SupportsHwaccel("vaapi")
|
||||||
|
&& string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
arg.Append("-hwaccel_output_format vaapi ")
|
if (isVaapiDecoder)
|
||||||
.Append("-vaapi_device ")
|
{
|
||||||
.Append(encodingOptions.VaapiDevice)
|
arg.Append("-hwaccel_output_format vaapi ")
|
||||||
.Append(" ");
|
.Append("-vaapi_device ")
|
||||||
|
.Append(encodingOptions.VaapiDevice)
|
||||||
|
.Append(' ');
|
||||||
|
}
|
||||||
|
else if (!isVaapiDecoder && isVaapiEncoder)
|
||||||
|
{
|
||||||
|
arg.Append("-vaapi_device ")
|
||||||
|
.Append(encodingOptions.VaapiDevice)
|
||||||
|
.Append(' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (!isVaapiDecoder && isVaapiEncoder)
|
|
||||||
{
|
|
||||||
arg.Append("-vaapi_device ")
|
|
||||||
.Append(encodingOptions.VaapiDevice)
|
|
||||||
.Append(" ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.IsVideoRequest
|
if (state.IsVideoRequest
|
||||||
&& string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
|
||||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
|
||||||
|
|
||||||
if (!hasTextSubs)
|
|
||||||
{
|
{
|
||||||
if (isQsvEncoder)
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
|
||||||
|
if (isQsvEncoder)
|
||||||
{
|
{
|
||||||
if (isQsvDecoder)
|
if (isQsvDecoder)
|
||||||
{
|
{
|
||||||
arg.Append("-hwaccel qsv -init_hw_device qsv=hw ");
|
if (isLinux)
|
||||||
|
{
|
||||||
|
if (hasGraphicalSubs)
|
||||||
|
{
|
||||||
|
arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
arg.Append("-hwaccel qsv ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindows)
|
||||||
|
{
|
||||||
|
arg.Append("-hwaccel qsv ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// While using SW decoder
|
// While using SW decoder
|
||||||
else
|
else
|
||||||
@ -806,6 +825,34 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
switch (encodingOptions.EncoderPreset)
|
||||||
|
{
|
||||||
|
case "veryslow":
|
||||||
|
case "slow":
|
||||||
|
case "slower":
|
||||||
|
param += "-quality quality";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "medium":
|
||||||
|
param += "-quality balanced";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "fast":
|
||||||
|
case "faster":
|
||||||
|
case "veryfast":
|
||||||
|
case "superfast":
|
||||||
|
case "ultrafast":
|
||||||
|
param += "-quality speed";
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
param += "-quality speed";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
|
else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase)) // webm
|
||||||
{
|
{
|
||||||
// Values 0-3, 0 being highest quality but slower
|
// Values 0-3, 0 being highest quality but slower
|
||||||
@ -1351,7 +1398,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
transcoderChannelLimit = 6;
|
transcoderChannelLimit = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
var isTranscodingAudio = !EncodingHelper.IsCopyCodec(codec);
|
var isTranscodingAudio = !IsCopyCodec(codec);
|
||||||
|
|
||||||
int? resultChannels = state.GetRequestedAudioChannels(codec);
|
int? resultChannels = state.GetRequestedAudioChannels(codec);
|
||||||
if (isTranscodingAudio)
|
if (isTranscodingAudio)
|
||||||
@ -1555,28 +1602,44 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
|
var index = outputSizeParam.IndexOf("hwdownload", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(index);
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
|
index = outputSizeParam.IndexOf("hwupload=extra_hw_frames", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(index);
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
|
index = outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(index);
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
|
index = outputSizeParam.IndexOf("yadif", StringComparison.OrdinalIgnoreCase);
|
||||||
if (index != -1)
|
if (index != -1)
|
||||||
{
|
{
|
||||||
outputSizeParam = "," + outputSizeParam.Substring(index);
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
index = outputSizeParam.IndexOf("vpp", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (index != -1)
|
||||||
|
{
|
||||||
|
outputSizeParam = outputSizeParam.Substring(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1585,43 +1648,30 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
var videoSizeParam = string.Empty;
|
var videoSizeParam = string.Empty;
|
||||||
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
|
var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
|
||||||
|
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
|
|
||||||
// Setup subtitle scaling
|
// Setup subtitle scaling
|
||||||
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
|
if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
|
||||||
{
|
{
|
||||||
videoSizeParam = string.Format(
|
// Adjust the size of graphical subtitles to fit the video stream.
|
||||||
CultureInfo.InvariantCulture,
|
var videoStream = state.VideoStream;
|
||||||
"scale={0}:{1}",
|
var inputWidth = videoStream?.Width;
|
||||||
state.VideoStream.Width.Value,
|
var inputHeight = videoStream?.Height;
|
||||||
state.VideoStream.Height.Value);
|
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
|
||||||
|
|
||||||
// For QSV, feed it into hardware encoder now
|
if (width.HasValue && height.HasValue)
|
||||||
if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
videoSizeParam += ",hwupload=extra_hw_frames=64";
|
videoSizeParam = string.Format(
|
||||||
}
|
|
||||||
|
|
||||||
// For VAAPI and CUVID decoder
|
|
||||||
// these encoders cannot automatically adjust the size of graphical subtitles to fit the output video,
|
|
||||||
// thus needs to be manually adjusted.
|
|
||||||
if (videoDecoder.IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
|
|
||||||
|| (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
|
||||||
&& (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
|
|
||||||
|| outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)))
|
|
||||||
{
|
|
||||||
var videoStream = state.VideoStream;
|
|
||||||
var inputWidth = videoStream?.Width;
|
|
||||||
var inputHeight = videoStream?.Height;
|
|
||||||
var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
|
|
||||||
|
|
||||||
if (width.HasValue && height.HasValue)
|
|
||||||
{
|
|
||||||
videoSizeParam = string.Format(
|
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"scale={0}:{1}",
|
"scale={0}x{1}",
|
||||||
width.Value,
|
width.Value,
|
||||||
height.Value);
|
height.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For QSV, feed it into hardware encoder now
|
||||||
|
if (isLinux && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
videoSizeParam += ",hwupload=extra_hw_frames=64";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1634,7 +1684,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
: state.SubtitleStream.Index;
|
: state.SubtitleStream.Index;
|
||||||
|
|
||||||
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
|
// Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
|
||||||
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
|
// Always put the scaler before the overlay for better performance
|
||||||
|
var retStr = !string.IsNullOrEmpty(outputSizeParam) ?
|
||||||
|
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"" :
|
||||||
|
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\"";
|
||||||
|
|
||||||
// When the input may or may not be hardware VAAPI decodable
|
// When the input may or may not be hardware VAAPI decodable
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -1644,12 +1697,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
[sub]: SW scaling subtitle to FixedOutputSize
|
[sub]: SW scaling subtitle to FixedOutputSize
|
||||||
[base][sub]: SW overlay
|
[base][sub]: SW overlay
|
||||||
*/
|
*/
|
||||||
outputSizeParam = outputSizeParam.TrimStart(',');
|
|
||||||
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
|
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||||
else if (IsVaapiSupported(state) && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
|
else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
|
||||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
@ -1657,7 +1709,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
[sub]: SW scaling subtitle to FixedOutputSize
|
[sub]: SW scaling subtitle to FixedOutputSize
|
||||||
[base][sub]: SW overlay
|
[base][sub]: SW overlay
|
||||||
*/
|
*/
|
||||||
outputSizeParam = outputSizeParam.TrimStart(',');
|
|
||||||
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
|
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
|
||||||
}
|
}
|
||||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
@ -1666,14 +1717,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
QSV in FFMpeg can now setup hardware overlay for transcodes.
|
QSV in FFMpeg can now setup hardware overlay for transcodes.
|
||||||
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
|
For software decoding and hardware encoding option, frames must be hwuploaded into hardware
|
||||||
with fixed frame size.
|
with fixed frame size.
|
||||||
|
Currently only supports linux.
|
||||||
*/
|
*/
|
||||||
if (videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1)
|
if (isLinux)
|
||||||
{
|
{
|
||||||
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
|
retStr = !string.IsNullOrEmpty(outputSizeParam) ?
|
||||||
}
|
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"" :
|
||||||
else
|
" -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\"";
|
||||||
{
|
|
||||||
retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwupload=extra_hw_frames=64[v];[v][sub]overlay_qsv=x=(W-w)/2:y=(H-h)/2{3}\"";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1745,10 +1795,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
requestedMaxWidth,
|
requestedMaxWidth,
|
||||||
requestedMaxHeight);
|
requestedMaxHeight);
|
||||||
|
|
||||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
|
||||||
|
|
||||||
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
||||||
|| (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !hasTextSubs))
|
|| string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||||
&& width.HasValue
|
&& width.HasValue
|
||||||
&& height.HasValue)
|
&& height.HasValue)
|
||||||
{
|
{
|
||||||
@ -1758,6 +1806,10 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var outputWidth = width.Value;
|
var outputWidth = width.Value;
|
||||||
var outputHeight = height.Value;
|
var outputHeight = height.Value;
|
||||||
var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
|
var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isDeintEnabled = state.DeInterlace("h264", true)
|
||||||
|
|| state.DeInterlace("avc", true)
|
||||||
|
|| state.DeInterlace("h265", true)
|
||||||
|
|| state.DeInterlace("hevc", true);
|
||||||
|
|
||||||
if (!videoWidth.HasValue
|
if (!videoWidth.HasValue
|
||||||
|| outputWidth != videoWidth.Value
|
|| outputWidth != videoWidth.Value
|
||||||
@ -1769,15 +1821,20 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
filters.Add(
|
filters.Add(
|
||||||
string.Format(
|
string.Format(
|
||||||
CultureInfo.InvariantCulture,
|
CultureInfo.InvariantCulture,
|
||||||
"{0}=w={1}:h={2}:format=nv12",
|
"{0}=w={1}:h={2}:format=nv12{3}",
|
||||||
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
|
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
|
||||||
outputWidth,
|
outputWidth,
|
||||||
outputHeight));
|
outputHeight,
|
||||||
|
(qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// set w=0:h=0 for vpp_qsv to keep the original dimensions, otherwise it will fail.
|
filters.Add(
|
||||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "{0}format=nv12", qsv_or_vaapi ? "vpp_qsv=w=0:h=0:" : "scale_vaapi="));
|
string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"{0}=format=nv12{1}",
|
||||||
|
qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
|
||||||
|
(qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
|
else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
|
||||||
@ -1989,7 +2046,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
// http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
|
||||||
|
|
||||||
var request = state.BaseRequest;
|
var request = state.BaseRequest;
|
||||||
|
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
var filters = new List<string>();
|
var filters = new List<string>();
|
||||||
|
|
||||||
@ -1998,32 +2054,34 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
var inputHeight = videoStream?.Height;
|
var inputHeight = videoStream?.Height;
|
||||||
var threeDFormat = state.MediaSource.Video3DFormat;
|
var threeDFormat = state.MediaSource.Video3DFormat;
|
||||||
|
|
||||||
|
var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isNvdecH264Decoder = videoDecoder.IndexOf("h264_cuvid", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
|
||||||
|
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
|
|
||||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||||
|
|
||||||
// When the input may or may not be hardware VAAPI decodable
|
// When the input may or may not be hardware VAAPI decodable
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
filters.Add("format=nv12|vaapi");
|
filters.Add("format=nv12|vaapi");
|
||||||
filters.Add("hwupload");
|
filters.Add("hwupload");
|
||||||
}
|
}
|
||||||
|
|
||||||
// When the input may or may not be hardware QSV decodable
|
// When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context
|
||||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
else if (isLinux && hasGraphicalSubs && isQsvH264Encoder)
|
||||||
{
|
{
|
||||||
if (!hasTextSubs)
|
filters.Add("hwupload=extra_hw_frames=64");
|
||||||
{
|
|
||||||
filters.Add("format=nv12|qsv");
|
|
||||||
filters.Add("hwupload=extra_hw_frames=64");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||||
else if (videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
|
else if (IsVaapiSupported(state) && isVaapiDecoder && isLibX264Encoder)
|
||||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
var codec = videoStream.Codec.ToLowerInvariant();
|
var codec = videoStream.Codec.ToLowerInvariant();
|
||||||
var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
var isColorDepth10 = IsColorDepth10(state);
|
||||||
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
// Assert 10-bit hardware VAAPI decodable
|
// Assert 10-bit hardware VAAPI decodable
|
||||||
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -2048,49 +2106,49 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add hardware deinterlace filter before scaling filter
|
// Add hardware deinterlace filter before scaling filter
|
||||||
if (state.DeInterlace("h264", true))
|
if (state.DeInterlace("h264", true) || state.DeInterlace("avc", true))
|
||||||
{
|
{
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
|
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_vaapi"));
|
||||||
}
|
}
|
||||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (!hasTextSubs)
|
|
||||||
{
|
|
||||||
filters.Add(string.Format(CultureInfo.InvariantCulture, "deinterlace_qsv"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add software deinterlace filter before scaling filter
|
// Add software deinterlace filter before scaling filter
|
||||||
if (((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true))
|
if (state.DeInterlace("h264", true)
|
||||||
&& !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|
|| state.DeInterlace("avc", true)
|
||||||
&& !string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
|| state.DeInterlace("h265", true)
|
||||||
|| (hasTextSubs && state.DeInterlace("h264", true) && string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)))
|
|| state.DeInterlace("hevc", true))
|
||||||
{
|
{
|
||||||
|
var deintParam = string.Empty;
|
||||||
var inputFramerate = videoStream?.RealFrameRate;
|
var inputFramerate = videoStream?.RealFrameRate;
|
||||||
|
|
||||||
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
|
// If it is already 60fps then it will create an output framerate that is much too high for roku and others to handle
|
||||||
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
|
if (string.Equals(options.DeinterlaceMethod, "yadif_bob", StringComparison.OrdinalIgnoreCase) && (inputFramerate ?? 60) <= 30)
|
||||||
{
|
{
|
||||||
filters.Add("yadif=1:-1:0");
|
deintParam = "yadif=1:-1:0";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
filters.Add("yadif=0:-1:0");
|
deintParam = "yadif=0:-1:0";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(deintParam))
|
||||||
|
{
|
||||||
|
if (!isVaapiH264Encoder && !isQsvH264Encoder && !isNvdecH264Decoder)
|
||||||
|
{
|
||||||
|
filters.Add(deintParam);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
|
// Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
|
||||||
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
||||||
|
|
||||||
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
|
// Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
if (state.SubtitleStream != null
|
if (hasTextSubs)
|
||||||
&& state.SubtitleStream.IsTextSubtitleStream
|
|
||||||
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
|
||||||
{
|
{
|
||||||
// Test passed on Intel and AMD gfx
|
// Test passed on Intel and AMD gfx
|
||||||
filters.Add("hwmap=mode=read+write");
|
filters.Add("hwmap=mode=read+write");
|
||||||
@ -2100,9 +2158,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
var output = string.Empty;
|
var output = string.Empty;
|
||||||
|
|
||||||
if (state.SubtitleStream != null
|
if (hasTextSubs)
|
||||||
&& state.SubtitleStream.IsTextSubtitleStream
|
|
||||||
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode)
|
|
||||||
{
|
{
|
||||||
var subParam = GetTextSubtitleParam(state);
|
var subParam = GetTextSubtitleParam(state);
|
||||||
|
|
||||||
@ -2110,7 +2166,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
// Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
|
// Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
|
||||||
// Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
|
// Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
|
||||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
if (isVaapiH264Encoder)
|
||||||
{
|
{
|
||||||
filters.Add("hwmap");
|
filters.Add("hwmap");
|
||||||
}
|
}
|
||||||
@ -2264,7 +2320,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
flags.Add("+ignidx");
|
flags.Add("+ignidx");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.GenPtsInput || EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
if (state.GenPtsInput || IsCopyCodec(state.OutputVideoCodec))
|
||||||
{
|
{
|
||||||
flags.Add("+genpts");
|
flags.Add("+genpts");
|
||||||
}
|
}
|
||||||
@ -2290,7 +2346,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
inputModifier += " " + videoDecoder;
|
inputModifier += " " + videoDecoder;
|
||||||
|
|
||||||
if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
|
if (!IsCopyCodec(state.OutputVideoCodec)
|
||||||
|
&& (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
{
|
{
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
var inputWidth = videoStream?.Width;
|
var inputWidth = videoStream?.Width;
|
||||||
@ -2303,11 +2360,19 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
&& width.HasValue
|
&& width.HasValue
|
||||||
&& height.HasValue)
|
&& height.HasValue)
|
||||||
{
|
{
|
||||||
inputModifier += string.Format(
|
if (width.HasValue && height.HasValue)
|
||||||
CultureInfo.InvariantCulture,
|
{
|
||||||
" -resize {0}x{1}",
|
inputModifier += string.Format(
|
||||||
width.Value,
|
CultureInfo.InvariantCulture,
|
||||||
height.Value);
|
" -resize {0}x{1}",
|
||||||
|
width.Value,
|
||||||
|
height.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.DeInterlace("h264", true))
|
||||||
|
{
|
||||||
|
inputModifier += " -deint 1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2523,21 +2588,21 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the output video codec.
|
/// Gets the ffmpeg option string for the hardware accelerated video decoder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="state">The encoding job info.</param>
|
||||||
|
/// <param name="encodingOptions">The encoding options.</param>
|
||||||
|
/// <returns>The option string or null if none available.</returns>
|
||||||
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
|
||||||
{
|
{
|
||||||
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
|
|
||||||
var videoStream = state.VideoStream;
|
var videoStream = state.VideoStream;
|
||||||
var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
|
||||||
|
if (videoStream == null)
|
||||||
if (EncodingHelper.IsCopyCodec(state.OutputVideoCodec))
|
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
|
||||||
// Only use alternative encoders for video files.
|
// Only use alternative encoders for video files.
|
||||||
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
// When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
|
||||||
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
// Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this.
|
||||||
@ -2546,10 +2611,15 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (videoStream != null
|
if (IsCopyCodec(state.OutputVideoCodec))
|
||||||
&& !string.IsNullOrEmpty(videoStream.Codec)
|
|
||||||
&& !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
|
|
||||||
{
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
|
||||||
|
{
|
||||||
|
var isColorDepth10 = IsColorDepth10(state);
|
||||||
|
|
||||||
// Only hevc and vp9 formats have 10-bit hardware decoder support now.
|
// Only hevc and vp9 formats have 10-bit hardware decoder support now.
|
||||||
if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||||
|| string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
|
|| string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -2625,13 +2695,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
case "h264":
|
case "h264":
|
||||||
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
|
if (_mediaEncoder.SupportsDecoder("h264_cuvid") && encodingOptions.HardwareDecodingCodecs.Contains("h264", StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// cuvid decoder does not support 10-bit input.
|
|
||||||
if ((videoStream.BitDepth ?? 8) > 8)
|
|
||||||
{
|
|
||||||
encodingOptions.HardwareDecodingCodecs = Array.Empty<string>();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return "-c:v h264_cuvid";
|
return "-c:v h264_cuvid";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2898,21 +2961,24 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
|
public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec)
|
||||||
{
|
{
|
||||||
var isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT;
|
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
|
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
|
var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
|
||||||
var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
|
var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
|
||||||
|
|
||||||
if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
|
if ((isDxvaSupported || IsVaapiSupported(state)) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!isWindows)
|
if (isLinux)
|
||||||
{
|
{
|
||||||
return "-hwaccel vaapi";
|
return "-hwaccel vaapi";
|
||||||
}
|
}
|
||||||
else if (isWindows8orLater)
|
|
||||||
|
if (isWindows && isWindows8orLater)
|
||||||
{
|
{
|
||||||
return "-hwaccel d3d11va";
|
return "-hwaccel d3d11va";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (isWindows && !isWindows8orLater)
|
||||||
{
|
{
|
||||||
return "-hwaccel dxva2";
|
return "-hwaccel dxva2";
|
||||||
}
|
}
|
||||||
@ -3002,7 +3068,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
args += " -mpegts_m2ts_mode 1";
|
args += " -mpegts_m2ts_mode 1";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EncodingHelper.IsCopyCodec(videoCodec))
|
if (IsCopyCodec(videoCodec))
|
||||||
{
|
{
|
||||||
if (state.VideoStream != null
|
if (state.VideoStream != null
|
||||||
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
|
||||||
@ -3104,7 +3170,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
|
|
||||||
var args = "-codec:a:0 " + codec;
|
var args = "-codec:a:0 " + codec;
|
||||||
|
|
||||||
if (EncodingHelper.IsCopyCodec(codec))
|
if (IsCopyCodec(codec))
|
||||||
{
|
{
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
@ -3181,5 +3247,42 @@ namespace MediaBrowser.Controller.MediaEncoding
|
|||||||
{
|
{
|
||||||
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsColorDepth10(EncodingJobInfo state)
|
||||||
|
{
|
||||||
|
var result = false;
|
||||||
|
var videoStream = state.VideoStream;
|
||||||
|
|
||||||
|
if (videoStream != null)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(videoStream.PixelFormat))
|
||||||
|
{
|
||||||
|
result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(videoStream.Profile))
|
||||||
|
{
|
||||||
|
result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase);
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = (videoStream.BitDepth ?? 8) == 10;
|
||||||
|
if (result)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,7 +198,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
internal static Version GetFFmpegVersion(string output)
|
internal static Version GetFFmpegVersion(string output)
|
||||||
{
|
{
|
||||||
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
// For pre-built binaries the FFmpeg version should be mentioned at the very start of the output
|
||||||
var match = Regex.Match(output, @"^ffmpeg version n?((?:\d+\.?)+)");
|
var match = Regex.Match(output, @"^ffmpeg version n?((?:[0-9]+\.?)+)");
|
||||||
|
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
@ -225,7 +225,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
|||||||
var rc = new StringBuilder(144);
|
var rc = new StringBuilder(144);
|
||||||
foreach (Match m in Regex.Matches(
|
foreach (Match m in Regex.Matches(
|
||||||
output,
|
output,
|
||||||
@"((?<name>lib\w+)\s+(?<major>\d+)\.\s*(?<minor>\d+))",
|
@"((?<name>lib\w+)\s+(?<major>[0-9]+)\.\s*(?<minor>[0-9]+))",
|
||||||
RegexOptions.Multiline))
|
RegexOptions.Multiline))
|
||||||
{
|
{
|
||||||
rc.Append(m.Groups["name"])
|
rc.Append(m.Groups["name"])
|
||||||
|
@ -172,7 +172,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
|||||||
inputFiles = new[] { mediaSource.Path };
|
inputFiles = new[] { mediaSource.Path };
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, mediaSource.Protocol, subtitleStream, cancellationToken).ConfigureAwait(false);
|
var fileInfo = await GetReadableFile(mediaSource.Path, inputFiles, _mediaSourceManager.GetPathProtocol(subtitleStream.Path), subtitleStream, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
|
var stream = await GetSubtitleStream(fileInfo.Path, fileInfo.Protocol, fileInfo.IsExternal, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#pragma warning disable CA1819 // Properties should not return arrays
|
||||||
#pragma warning disable CS1591
|
#pragma warning disable CS1591
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
@ -9,21 +10,27 @@ namespace MediaBrowser.Model.Notifications
|
|||||||
public NotificationOption(string type)
|
public NotificationOption(string type)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
DisabledServices = Array.Empty<string>();
|
DisabledServices = Array.Empty<string>();
|
||||||
DisabledMonitorUsers = Array.Empty<string>();
|
DisabledMonitorUsers = Array.Empty<string>();
|
||||||
SendToUsers = Array.Empty<string>();
|
SendToUsers = Array.Empty<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Type { get; set; }
|
public NotificationOption()
|
||||||
|
{
|
||||||
|
DisabledServices = Array.Empty<string>();
|
||||||
|
DisabledMonitorUsers = Array.Empty<string>();
|
||||||
|
SendToUsers = Array.Empty<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string? Type { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User Ids to not monitor (it's opt out).
|
/// Gets or sets user Ids to not monitor (it's opt out).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] DisabledMonitorUsers { get; set; }
|
public string[] DisabledMonitorUsers { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// User Ids to send to (if SendToUserMode == Custom)
|
/// Gets or sets user Ids to send to (if SendToUserMode == Custom).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string[] SendToUsers { get; set; }
|
public string[] SendToUsers { get; set; }
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -25,7 +23,6 @@ using MediaBrowser.Model.Entities;
|
|||||||
using MediaBrowser.Model.Events;
|
using MediaBrowser.Model.Events;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
using MediaBrowser.Model.Providers;
|
using MediaBrowser.Model.Providers;
|
||||||
using MediaBrowser.Model.Serialization;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Priority_Queue;
|
using Priority_Queue;
|
||||||
using Book = MediaBrowser.Controller.Entities.Book;
|
using Book = MediaBrowser.Controller.Entities.Book;
|
||||||
@ -42,33 +39,38 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ProviderManager : IProviderManager, IDisposable
|
public class ProviderManager : IProviderManager, IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly object _refreshQueueLock = new object();
|
||||||
private readonly ILogger<ProviderManager> _logger;
|
private readonly ILogger<ProviderManager> _logger;
|
||||||
private readonly IHttpClient _httpClient;
|
private readonly IHttpClient _httpClient;
|
||||||
private readonly ILibraryMonitor _libraryMonitor;
|
private readonly ILibraryMonitor _libraryMonitor;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IServerApplicationPaths _appPaths;
|
private readonly IServerApplicationPaths _appPaths;
|
||||||
private readonly IJsonSerializer _json;
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ISubtitleManager _subtitleManager;
|
private readonly ISubtitleManager _subtitleManager;
|
||||||
private readonly IServerConfigurationManager _configurationManager;
|
private readonly IServerConfigurationManager _configurationManager;
|
||||||
|
private readonly ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
|
||||||
|
private readonly CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
|
||||||
|
new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
|
||||||
|
|
||||||
private IImageProvider[] ImageProviders { get; set; }
|
private IMetadataService[] _metadataServices = Array.Empty<IMetadataService>();
|
||||||
|
private IMetadataProvider[] _metadataProviders = Array.Empty<IMetadataProvider>();
|
||||||
private IMetadataService[] _metadataServices = { };
|
|
||||||
private IMetadataProvider[] _metadataProviders = { };
|
|
||||||
private IEnumerable<IMetadataSaver> _savers;
|
private IEnumerable<IMetadataSaver> _savers;
|
||||||
|
|
||||||
private IExternalId[] _externalIds;
|
private IExternalId[] _externalIds;
|
||||||
|
private bool _isProcessingRefreshQueue;
|
||||||
private CancellationTokenSource _disposeCancellationTokenSource = new CancellationTokenSource();
|
private bool _disposed;
|
||||||
|
|
||||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
|
|
||||||
public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
|
|
||||||
public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ProviderManager" /> class.
|
/// Initializes a new instance of the <see cref="ProviderManager"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="httpClient">The Http client.</param>
|
||||||
|
/// <param name="subtitleManager">The subtitle manager.</param>
|
||||||
|
/// <param name="configurationManager">The configuration manager.</param>
|
||||||
|
/// <param name="libraryMonitor">The library monitor.</param>
|
||||||
|
/// <param name="logger">The logger.</param>
|
||||||
|
/// <param name="fileSystem">The filesystem.</param>
|
||||||
|
/// <param name="appPaths">The server application paths.</param>
|
||||||
|
/// <param name="libraryManager">The library manager.</param>
|
||||||
public ProviderManager(
|
public ProviderManager(
|
||||||
IHttpClient httpClient,
|
IHttpClient httpClient,
|
||||||
ISubtitleManager subtitleManager,
|
ISubtitleManager subtitleManager,
|
||||||
@ -77,8 +79,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
ILogger<ProviderManager> logger,
|
ILogger<ProviderManager> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem,
|
||||||
IServerApplicationPaths appPaths,
|
IServerApplicationPaths appPaths,
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager)
|
||||||
IJsonSerializer json)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
@ -87,16 +88,27 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_appPaths = appPaths;
|
_appPaths = appPaths;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_json = json;
|
|
||||||
_subtitleManager = subtitleManager;
|
_subtitleManager = subtitleManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Adds the metadata providers.
|
public event EventHandler<GenericEventArgs<BaseItem>> RefreshStarted;
|
||||||
/// </summary>
|
|
||||||
public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices,
|
/// <inheritdoc/>
|
||||||
IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
|
public event EventHandler<GenericEventArgs<BaseItem>> RefreshCompleted;
|
||||||
IEnumerable<IExternalId> externalIds)
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public event EventHandler<GenericEventArgs<Tuple<BaseItem, double>>> RefreshProgress;
|
||||||
|
|
||||||
|
private IImageProvider[] ImageProviders { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void AddParts(
|
||||||
|
IEnumerable<IImageProvider> imageProviders,
|
||||||
|
IEnumerable<IMetadataService> metadataServices,
|
||||||
|
IEnumerable<IMetadataProvider> metadataProviders,
|
||||||
|
IEnumerable<IMetadataSaver> metadataSavers,
|
||||||
|
IEnumerable<IExternalId> externalIds)
|
||||||
{
|
{
|
||||||
ImageProviders = imageProviders.ToArray();
|
ImageProviders = imageProviders.ToArray();
|
||||||
|
|
||||||
@ -104,27 +116,17 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
_metadataProviders = metadataProviders.ToArray();
|
_metadataProviders = metadataProviders.ToArray();
|
||||||
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
|
_externalIds = externalIds.OrderBy(i => i.ProviderName).ToArray();
|
||||||
|
|
||||||
_savers = metadataSavers.Where(i =>
|
_savers = metadataSavers
|
||||||
{
|
.Where(i => !(i is IConfigurableProvider configurable) || configurable.IsEnabled)
|
||||||
var configurable = i as IConfigurableProvider;
|
.ToArray();
|
||||||
|
|
||||||
return configurable == null || configurable.IsEnabled;
|
|
||||||
}).ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
public Task<ItemUpdateType> RefreshSingleItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
IMetadataService service = null;
|
|
||||||
var type = item.GetType();
|
var type = item.GetType();
|
||||||
|
|
||||||
foreach (var current in _metadataServices)
|
var service = _metadataServices.FirstOrDefault(current => current.CanRefreshPrimary(type));
|
||||||
{
|
|
||||||
if (current.CanRefreshPrimary(type))
|
|
||||||
{
|
|
||||||
service = current;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (service == null)
|
if (service == null)
|
||||||
{
|
{
|
||||||
@ -147,35 +149,36 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return Task.FromResult(ItemUpdateType.None);
|
return Task.FromResult(ItemUpdateType.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
public async Task SaveImage(BaseItem item, string url, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.GetResponse(new HttpRequestOptions
|
using var response = await _httpClient.GetResponse(new HttpRequestOptions
|
||||||
{
|
{
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
Url = url,
|
Url = url,
|
||||||
BufferContent = false
|
BufferContent = false
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
}).ConfigureAwait(false))
|
// Workaround for tvheadend channel icons
|
||||||
|
// TODO: Isolate this hack into the tvh plugin
|
||||||
|
if (string.IsNullOrEmpty(response.ContentType))
|
||||||
{
|
{
|
||||||
// Workaround for tvheadend channel icons
|
if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
// TODO: Isolate this hack into the tvh plugin
|
|
||||||
if (string.IsNullOrEmpty(response.ContentType))
|
|
||||||
{
|
{
|
||||||
if (url.IndexOf("/imagecache/", StringComparison.OrdinalIgnoreCase) != -1)
|
response.ContentType = "image/png";
|
||||||
{
|
|
||||||
response.ContentType = "image/png";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
|
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
|
public Task SaveImage(BaseItem item, string source, string mimeType, ImageType type, int? imageIndex, bool? saveLocallyWithMedia, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(source))
|
if (string.IsNullOrWhiteSpace(source))
|
||||||
@ -188,12 +191,14 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
|
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger).SaveImage(item, fileStream, mimeType, type, imageIndex, saveLocallyWithMedia, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task SaveImage(User user, Stream source, string mimeType, string path)
|
public Task SaveImage(User user, Stream source, string mimeType, string path)
|
||||||
{
|
{
|
||||||
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
|
return new ImageSaver(_configurationManager, _libraryMonitor, _fileSystem, _logger)
|
||||||
.SaveImage(user, source, path);
|
.SaveImage(user, source, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
|
public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, RemoteImageQuery query, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
|
var providers = GetRemoteImageProviders(item, query.IncludeDisabledProviders);
|
||||||
@ -213,7 +218,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
languages.Add(preferredLanguage);
|
languages.Add(preferredLanguage);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tasks = providers.Select(i => GetImages(item, cancellationToken, i, languages, query.ImageType));
|
var tasks = providers.Select(i => GetImages(item, i, languages, cancellationToken, query.ImageType));
|
||||||
|
|
||||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||||
|
|
||||||
@ -224,12 +229,17 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
/// Gets the images.
|
/// Gets the images.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="cancellationToken">The cancellation token.</param>
|
|
||||||
/// <param name="provider">The provider.</param>
|
/// <param name="provider">The provider.</param>
|
||||||
/// <param name="preferredLanguages">The preferred languages.</param>
|
/// <param name="preferredLanguages">The preferred languages.</param>
|
||||||
|
/// <param name="cancellationToken">The cancellation token.</param>
|
||||||
/// <param name="type">The type.</param>
|
/// <param name="type">The type.</param>
|
||||||
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
|
/// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
|
||||||
private async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, CancellationToken cancellationToken, IRemoteImageProvider provider, List<string> preferredLanguages, ImageType? type = null)
|
private async Task<IEnumerable<RemoteImageInfo>> GetImages(
|
||||||
|
BaseItem item,
|
||||||
|
IRemoteImageProvider provider,
|
||||||
|
IReadOnlyCollection<string> preferredLanguages,
|
||||||
|
CancellationToken cancellationToken,
|
||||||
|
ImageType? type = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -255,21 +265,23 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "{0} failed in GetImageInfos for type {1}", provider.GetType().Name, item.GetType().Name);
|
_logger.LogError(ex, "{ProviderName} failed in GetImageInfos for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
||||||
return new List<RemoteImageInfo>();
|
return new List<RemoteImageInfo>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the supported image providers.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="item">The item.</param>
|
|
||||||
/// <returns>IEnumerable{IImageProvider}.</returns>
|
|
||||||
public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
|
public IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray()));
|
return GetRemoteImageProviders(item, true).Select(i => new ImageProviderInfo(i.Name, i.GetSupportedImages(item).ToArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the image providers for the provided item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="refreshOptions">The image refresh options.</param>
|
||||||
|
/// <returns>The image providers for the item.</returns>
|
||||||
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
|
public IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions)
|
||||||
{
|
{
|
||||||
return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
|
return GetImageProviders(item, _libraryManager.GetLibraryOptions(item), GetMetadataOptions(item), refreshOptions, false);
|
||||||
@ -283,7 +295,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
||||||
var typeFetcherOrder = typeOptions?.ImageFetcherOrder;
|
var typeFetcherOrder = typeOptions?.ImageFetcherOrder;
|
||||||
|
|
||||||
return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, options, refreshOptions, includeDisabled))
|
return ImageProviders.Where(i => CanRefresh(i, item, libraryOptions, refreshOptions, includeDisabled))
|
||||||
.OrderBy(i =>
|
.OrderBy(i =>
|
||||||
{
|
{
|
||||||
// See if there's a user-defined order
|
// See if there's a user-defined order
|
||||||
@ -304,6 +316,13 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
.ThenBy(GetOrder);
|
.ThenBy(GetOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the metadata providers for the provided item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item.</param>
|
||||||
|
/// <param name="libraryOptions">The library options.</param>
|
||||||
|
/// <typeparam name="T">The type of metadata provider.</typeparam>
|
||||||
|
/// <returns>The metadata providers.</returns>
|
||||||
public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
|
public IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
|
||||||
where T : BaseItem
|
where T : BaseItem
|
||||||
{
|
{
|
||||||
@ -319,7 +338,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
var currentOptions = globalMetadataOptions;
|
var currentOptions = globalMetadataOptions;
|
||||||
|
|
||||||
return _metadataProviders.OfType<IMetadataProvider<T>>()
|
return _metadataProviders.OfType<IMetadataProvider<T>>()
|
||||||
.Where(i => CanRefresh(i, item, libraryOptions, currentOptions, includeDisabled, forceEnableInternetMetadata))
|
.Where(i => CanRefresh(i, item, libraryOptions, includeDisabled, forceEnableInternetMetadata))
|
||||||
.OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions))
|
.OrderBy(i => GetConfiguredOrder(item, i, libraryOptions, globalMetadataOptions))
|
||||||
.ThenBy(GetDefaultOrder);
|
.ThenBy(GetDefaultOrder);
|
||||||
}
|
}
|
||||||
@ -329,14 +348,20 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
var options = GetMetadataOptions(item);
|
var options = GetMetadataOptions(item);
|
||||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||||
|
|
||||||
return GetImageProviders(item, libraryOptions, options,
|
return GetImageProviders(
|
||||||
new ImageRefreshOptions(
|
item,
|
||||||
new DirectoryService(_fileSystem)),
|
libraryOptions,
|
||||||
includeDisabled)
|
options,
|
||||||
.OfType<IRemoteImageProvider>();
|
new ImageRefreshOptions(new DirectoryService(_fileSystem)),
|
||||||
|
includeDisabled).OfType<IRemoteImageProvider>();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRefresh(IMetadataProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, bool includeDisabled, bool forceEnableInternetMetadata)
|
private bool CanRefresh(
|
||||||
|
IMetadataProvider provider,
|
||||||
|
BaseItem item,
|
||||||
|
LibraryOptions libraryOptions,
|
||||||
|
bool includeDisabled,
|
||||||
|
bool forceEnableInternetMetadata)
|
||||||
{
|
{
|
||||||
if (!includeDisabled)
|
if (!includeDisabled)
|
||||||
{
|
{
|
||||||
@ -372,7 +397,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CanRefresh(IImageProvider provider, BaseItem item, LibraryOptions libraryOptions, MetadataOptions options, ImageRefreshOptions refreshOptions, bool includeDisabled)
|
private bool CanRefresh(
|
||||||
|
IImageProvider provider,
|
||||||
|
BaseItem item,
|
||||||
|
LibraryOptions libraryOptions,
|
||||||
|
ImageRefreshOptions refreshOptions,
|
||||||
|
bool includeDisabled)
|
||||||
{
|
{
|
||||||
if (!includeDisabled)
|
if (!includeDisabled)
|
||||||
{
|
{
|
||||||
@ -400,7 +430,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "{0} failed in Supports for type {1}", provider.GetType().Name, item.GetType().Name);
|
_logger.LogError(ex, "{ProviderName} failed in Supports for type {ItemType} at {ItemPath}", provider.GetType().Name, item.GetType().Name, item.Path);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -412,9 +442,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
/// <returns>System.Int32.</returns>
|
/// <returns>System.Int32.</returns>
|
||||||
private int GetOrder(IImageProvider provider)
|
private int GetOrder(IImageProvider provider)
|
||||||
{
|
{
|
||||||
var hasOrder = provider as IHasOrder;
|
if (!(provider is IHasOrder hasOrder))
|
||||||
|
|
||||||
if (hasOrder == null)
|
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -441,7 +469,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
if (provider is IRemoteMetadataProvider)
|
if (provider is IRemoteMetadataProvider)
|
||||||
{
|
{
|
||||||
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
var typeOptions = libraryOptions.GetTypeOptions(item.GetType().Name);
|
||||||
var typeFetcherOrder = typeOptions == null ? null : typeOptions.MetadataFetcherOrder;
|
var typeFetcherOrder = typeOptions?.MetadataFetcherOrder;
|
||||||
|
|
||||||
var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
|
var fetcherOrder = typeFetcherOrder ?? globalMetadataOptions.MetadataFetcherOrder;
|
||||||
|
|
||||||
@ -459,9 +487,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
private int GetDefaultOrder(IMetadataProvider provider)
|
private int GetDefaultOrder(IMetadataProvider provider)
|
||||||
{
|
{
|
||||||
var hasOrder = provider as IHasOrder;
|
if (provider is IHasOrder hasOrder)
|
||||||
|
|
||||||
if (hasOrder != null)
|
|
||||||
{
|
{
|
||||||
return hasOrder.Order;
|
return hasOrder.Order;
|
||||||
}
|
}
|
||||||
@ -469,9 +495,10 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public MetadataPluginSummary[] GetAllMetadataPlugins()
|
public MetadataPluginSummary[] GetAllMetadataPlugins()
|
||||||
{
|
{
|
||||||
return new MetadataPluginSummary[]
|
return new[]
|
||||||
{
|
{
|
||||||
GetPluginSummary<Movie>(),
|
GetPluginSummary<Movie>(),
|
||||||
GetPluginSummary<BoxSet>(),
|
GetPluginSummary<BoxSet>(),
|
||||||
@ -493,7 +520,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
where T : BaseItem, new()
|
where T : BaseItem, new()
|
||||||
{
|
{
|
||||||
// Give it a dummy path just so that it looks like a file system item
|
// Give it a dummy path just so that it looks like a file system item
|
||||||
var dummy = new T()
|
var dummy = new T
|
||||||
{
|
{
|
||||||
Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
|
Path = Path.Combine(_appPaths.InternalMetadataPath, "dummy"),
|
||||||
ParentId = Guid.NewGuid()
|
ParentId = Guid.NewGuid()
|
||||||
@ -508,11 +535,12 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
var libraryOptions = new LibraryOptions();
|
var libraryOptions = new LibraryOptions();
|
||||||
|
|
||||||
var imageProviders = GetImageProviders(dummy, libraryOptions, options,
|
var imageProviders = GetImageProviders(
|
||||||
new ImageRefreshOptions(
|
dummy,
|
||||||
new DirectoryService(_fileSystem)),
|
libraryOptions,
|
||||||
true)
|
options,
|
||||||
.ToList();
|
new ImageRefreshOptions(new DirectoryService(_fileSystem)),
|
||||||
|
true).ToList();
|
||||||
|
|
||||||
var pluginList = summary.Plugins.ToList();
|
var pluginList = summary.Plugins.ToList();
|
||||||
|
|
||||||
@ -572,7 +600,6 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
|
private void AddImagePlugins<T>(List<MetadataPlugin> list, T item, List<IImageProvider> imageProviders)
|
||||||
where T : BaseItem
|
where T : BaseItem
|
||||||
{
|
{
|
||||||
|
|
||||||
// Locals
|
// Locals
|
||||||
list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
|
list.AddRange(imageProviders.Where(i => (i is ILocalImageProvider)).Select(i => new MetadataPlugin
|
||||||
{
|
{
|
||||||
@ -588,6 +615,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public MetadataOptions GetMetadataOptions(BaseItem item)
|
public MetadataOptions GetMetadataOptions(BaseItem item)
|
||||||
{
|
{
|
||||||
var type = item.GetType().Name;
|
var type = item.GetType().Name;
|
||||||
@ -597,17 +625,13 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
new MetadataOptions();
|
new MetadataOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Saves the metadata.
|
|
||||||
/// </summary>
|
|
||||||
public void SaveMetadata(BaseItem item, ItemUpdateType updateType)
|
public void SaveMetadata(BaseItem item, ItemUpdateType updateType)
|
||||||
{
|
{
|
||||||
SaveMetadata(item, updateType, _savers);
|
SaveMetadata(item, updateType, _savers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Saves the metadata.
|
|
||||||
/// </summary>
|
|
||||||
public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
|
public void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<string> savers)
|
||||||
{
|
{
|
||||||
SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)));
|
SaveMetadata(item, updateType, _savers.Where(i => savers.Contains(i.Name, StringComparer.OrdinalIgnoreCase)));
|
||||||
@ -619,7 +643,6 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
/// <param name="item">The item.</param>
|
/// <param name="item">The item.</param>
|
||||||
/// <param name="updateType">Type of the update.</param>
|
/// <param name="updateType">Type of the update.</param>
|
||||||
/// <param name="savers">The savers.</param>
|
/// <param name="savers">The savers.</param>
|
||||||
/// <returns>Task.</returns>
|
|
||||||
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
|
private void SaveMetadata(BaseItem item, ItemUpdateType updateType, IEnumerable<IMetadataSaver> savers)
|
||||||
{
|
{
|
||||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||||
@ -628,11 +651,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
_logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
|
_logger.LogDebug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
|
||||||
|
|
||||||
var fileSaver = saver as IMetadataFileSaver;
|
if (saver is IMetadataFileSaver fileSaver)
|
||||||
|
|
||||||
if (fileSaver != null)
|
|
||||||
{
|
{
|
||||||
string path = null;
|
string path;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -699,11 +720,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
if (updateType >= ItemUpdateType.MetadataEdit)
|
if (updateType >= ItemUpdateType.MetadataEdit)
|
||||||
{
|
{
|
||||||
var fileSaver = saver as IMetadataFileSaver;
|
|
||||||
|
|
||||||
// Manual edit occurred
|
// Manual edit occurred
|
||||||
// Even if save local is off, save locally anyway if the metadata file already exists
|
// Even if save local is off, save locally anyway if the metadata file already exists
|
||||||
if (fileSaver == null || !File.Exists(fileSaver.GetSavePath(item)))
|
if (!(saver is IMetadataFileSaver fileSaver) || !File.Exists(fileSaver.GetSavePath(item)))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -734,6 +753,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken)
|
public Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, CancellationToken cancellationToken)
|
||||||
where TItemType : BaseItem, new()
|
where TItemType : BaseItem, new()
|
||||||
where TLookupType : ItemLookupInfo
|
where TLookupType : ItemLookupInfo
|
||||||
@ -748,7 +768,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
|
return GetRemoteSearchResults<TItemType, TLookupType>(searchInfo, referenceItem, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
|
private async Task<IEnumerable<RemoteSearchResult>> GetRemoteSearchResults<TItemType, TLookupType>(RemoteSearchQuery<TLookupType> searchInfo, BaseItem referenceItem, CancellationToken cancellationToken)
|
||||||
where TItemType : BaseItem, new()
|
where TItemType : BaseItem, new()
|
||||||
where TLookupType : ItemLookupInfo
|
where TLookupType : ItemLookupInfo
|
||||||
{
|
{
|
||||||
@ -837,7 +857,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return resultList;
|
return resultList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(IRemoteSearchProvider<TLookupType> provider, TLookupType searchInfo,
|
private async Task<IEnumerable<RemoteSearchResult>> GetSearchResults<TLookupType>(
|
||||||
|
IRemoteSearchProvider<TLookupType> provider,
|
||||||
|
TLookupType searchInfo,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
where TLookupType : ItemLookupInfo
|
where TLookupType : ItemLookupInfo
|
||||||
{
|
{
|
||||||
@ -853,6 +875,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
|
public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
|
var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
|
||||||
@ -865,6 +888,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return provider.GetImageResponse(url, cancellationToken);
|
return provider.GetImageResponse(url, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
|
public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
|
||||||
{
|
{
|
||||||
return _externalIds.Where(i =>
|
return _externalIds.Where(i =>
|
||||||
@ -881,6 +905,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
|
public IEnumerable<ExternalUrl> GetExternalUrls(BaseItem item)
|
||||||
{
|
{
|
||||||
return GetExternalIds(item)
|
return GetExternalIds(item)
|
||||||
@ -909,6 +934,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}).Where(i => i != null).Concat(item.GetRelatedUrls());
|
}).Where(i => i != null).Concat(item.GetRelatedUrls());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
|
public IEnumerable<ExternalIdInfo> GetExternalIdInfos(IHasProviderIds item)
|
||||||
{
|
{
|
||||||
return GetExternalIds(item)
|
return GetExternalIds(item)
|
||||||
@ -921,8 +947,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConcurrentDictionary<Guid, double> _activeRefreshes = new ConcurrentDictionary<Guid, double>();
|
/// <inheritdoc/>
|
||||||
|
|
||||||
public Dictionary<Guid, Guid> GetRefreshQueue()
|
public Dictionary<Guid, Guid> GetRefreshQueue()
|
||||||
{
|
{
|
||||||
lock (_refreshQueueLock)
|
lock (_refreshQueueLock)
|
||||||
@ -938,6 +963,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void OnRefreshStart(BaseItem item)
|
public void OnRefreshStart(BaseItem item)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
_logger.LogInformation("OnRefreshStart {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
@ -945,6 +971,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
RefreshStarted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void OnRefreshComplete(BaseItem item)
|
public void OnRefreshComplete(BaseItem item)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
_logger.LogInformation("OnRefreshComplete {0}", item.Id.ToString("N", CultureInfo.InvariantCulture));
|
||||||
@ -954,6 +981,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
RefreshCompleted?.Invoke(this, new GenericEventArgs<BaseItem>(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public double? GetRefreshProgress(Guid id)
|
public double? GetRefreshProgress(Guid id)
|
||||||
{
|
{
|
||||||
if (_activeRefreshes.TryGetValue(id, out double value))
|
if (_activeRefreshes.TryGetValue(id, out double value))
|
||||||
@ -964,6 +992,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void OnRefreshProgress(BaseItem item, double progress)
|
public void OnRefreshProgress(BaseItem item, double progress)
|
||||||
{
|
{
|
||||||
var id = item.Id;
|
var id = item.Id;
|
||||||
@ -983,12 +1012,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
|
RefreshProgress?.Invoke(this, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(item, progress)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>> _refreshQueue =
|
/// <inheritdoc/>
|
||||||
new SimplePriorityQueue<Tuple<Guid, MetadataRefreshOptions>>();
|
|
||||||
|
|
||||||
private readonly object _refreshQueueLock = new object();
|
|
||||||
private bool _isProcessingRefreshQueue;
|
|
||||||
|
|
||||||
public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority)
|
public void QueueRefresh(Guid id, MetadataRefreshOptions options, RefreshPriority priority)
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
@ -1032,7 +1056,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
// Try to throttle this a little bit.
|
// Try to throttle this a little bit.
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
var task = item is MusicArtist artist
|
var task = item is MusicArtist artist
|
||||||
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
|
? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
|
||||||
@ -1062,17 +1086,14 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
// Collection folders don't validate their children so we'll have to simulate that here
|
// Collection folders don't validate their children so we'll have to simulate that here
|
||||||
|
switch (item)
|
||||||
if (item is CollectionFolder collectionFolder)
|
|
||||||
{
|
{
|
||||||
await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
|
case CollectionFolder collectionFolder:
|
||||||
}
|
await RefreshCollectionFolderChildren(options, collectionFolder, cancellationToken).ConfigureAwait(false);
|
||||||
else
|
break;
|
||||||
{
|
case Folder folder:
|
||||||
if (item is Folder folder)
|
|
||||||
{
|
|
||||||
await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
|
await folder.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
|
||||||
}
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1082,7 +1103,7 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
{
|
{
|
||||||
await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
await child.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options, true).ConfigureAwait(false);
|
await child.ValidateChildren(new SimpleProgress<double>(), cancellationToken, options).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1118,12 +1139,13 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
public Task RefreshFullItem(BaseItem item, MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return RefreshItem(item, options, cancellationToken);
|
return RefreshItem(item, options, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _disposed;
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.6" />
|
||||||
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
<PackageReference Include="OptimizedPriorityQueue" Version="4.2.0" />
|
||||||
<PackageReference Include="PlaylistsNET" Version="1.0.6" />
|
<PackageReference Include="PlaylistsNET" Version="1.0.6" />
|
||||||
<PackageReference Include="TvDbSharper" Version="3.2.0" />
|
<PackageReference Include="TvDbSharper" Version="3.2.0" />
|
||||||
|
@ -28,29 +28,31 @@
|
|||||||
pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
|
pluginId: "a629c0da-fac5-4c7e-931a-7174223f14c8"
|
||||||
};
|
};
|
||||||
|
|
||||||
$('.configPage').on('pageshow', function () {
|
document.querySelector('.configPage')
|
||||||
Dashboard.showLoadingMsg();
|
.addEventListener('pageshow', function () {
|
||||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
Dashboard.showLoadingMsg();
|
||||||
$('#enable').checked = config.Enable;
|
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||||
$('#replaceAlbumName').checked = config.ReplaceAlbumName;
|
document.querySelector('#enable').checked = config.Enable;
|
||||||
|
document.querySelector('#replaceAlbumName').checked = config.ReplaceAlbumName;
|
||||||
Dashboard.hideLoadingMsg();
|
|
||||||
});
|
Dashboard.hideLoadingMsg();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.configForm').on('submit', function (e) {
|
|
||||||
Dashboard.showLoadingMsg();
|
|
||||||
|
|
||||||
var form = this;
|
|
||||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
|
||||||
config.Enable = $('#enable', form).checked;
|
|
||||||
config.ReplaceAlbumName = $('#replaceAlbumName', form).checked;
|
|
||||||
|
|
||||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
document.querySelector('.configForm')
|
||||||
});
|
.addEventListener('submit', function (e) {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
|
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||||
|
config.Enable = document.querySelector('#enable').checked;
|
||||||
|
config.ReplaceAlbumName = document.querySelector('#replaceAlbumName').checked;
|
||||||
|
|
||||||
|
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -36,33 +36,47 @@
|
|||||||
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
uniquePluginId: "8c95c4d2-e50c-4fb0-a4f3-6c06ff0f9a1a"
|
||||||
};
|
};
|
||||||
|
|
||||||
$('.musicBrainzConfigPage').on('pageshow', function () {
|
document.querySelector('.musicBrainzConfigPage')
|
||||||
Dashboard.showLoadingMsg();
|
.addEventListener('pageshow', function () {
|
||||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
Dashboard.showLoadingMsg();
|
||||||
$('#server').val(config.Server).change();
|
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||||
$('#rateLimit').val(config.RateLimit).change();
|
var server = document.querySelector('#server');
|
||||||
$('#enable').checked = config.Enable;
|
server.value = config.Server;
|
||||||
$('#replaceArtistName').checked = config.ReplaceArtistName;
|
server.dispatchEvent(new Event('change', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
var rateLimit = document.querySelector('#rateLimit');
|
||||||
|
rateLimit.value = config.RateLimit;
|
||||||
|
rateLimit.dispatchEvent(new Event('change', {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
document.querySelector('#enable').checked = config.Enable;
|
||||||
|
document.querySelector('#replaceArtistName').checked = config.ReplaceArtistName;
|
||||||
|
|
||||||
Dashboard.hideLoadingMsg();
|
Dashboard.hideLoadingMsg();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
document.querySelector('.musicBrainzConfigForm')
|
||||||
$('.musicBrainzConfigForm').on('submit', function (e) {
|
.addEventListener('submit', function (e) {
|
||||||
Dashboard.showLoadingMsg();
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
var form = this;
|
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
||||||
ApiClient.getPluginConfiguration(MusicBrainzPluginConfig.uniquePluginId).then(function (config) {
|
config.Server = document.querySelector('#server').value;
|
||||||
config.Server = $('#server', form).val();
|
config.RateLimit = document.querySelector('#rateLimit').value;
|
||||||
config.RateLimit = $('#rateLimit', form).val();
|
config.Enable = document.querySelector('#enable').checked;
|
||||||
config.Enable = $('#enable', form).checked;
|
config.ReplaceArtistName = document.querySelector('#replaceArtistName').checked;
|
||||||
config.ReplaceArtistName = $('#replaceArtistName', form).checked;
|
|
||||||
|
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||||
ApiClient.updatePluginConfiguration(MusicBrainzPluginConfig.uniquePluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -24,25 +24,28 @@
|
|||||||
pluginId: "a628c0da-fac5-4c7e-9d1a-7134223f14c8"
|
pluginId: "a628c0da-fac5-4c7e-9d1a-7134223f14c8"
|
||||||
};
|
};
|
||||||
|
|
||||||
$('.configPage').on('pageshow', function () {
|
document.querySelector('.configPage')
|
||||||
Dashboard.showLoadingMsg();
|
.addEventListener('pageshow', function () {
|
||||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
Dashboard.showLoadingMsg();
|
||||||
$('#castAndCrew').checked = config.CastAndCrew;
|
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||||
Dashboard.hideLoadingMsg();
|
document.querySelector('#castAndCrew').checked = config.CastAndCrew;
|
||||||
});
|
Dashboard.hideLoadingMsg();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.configForm').on('submit', function (e) {
|
|
||||||
Dashboard.showLoadingMsg();
|
|
||||||
|
|
||||||
var form = this;
|
|
||||||
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
|
||||||
config.CastAndCrew = $('#castAndCrew', form).checked;
|
|
||||||
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
document.querySelector('.configForm')
|
||||||
|
.addEventListener('submit', function (e) {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
|
ApiClient.getPluginConfiguration(PluginConfig.pluginId).then(function (config) {
|
||||||
|
config.CastAndCrew = document.querySelector('#castAndCrew').checked;
|
||||||
|
ApiClient.updatePluginConfiguration(PluginConfig.pluginId, config).then(Dashboard.processPluginConfigurationUpdateResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MediaBrowser.Controller.Providers;
|
using MediaBrowser.Controller.Providers;
|
||||||
@ -229,6 +230,45 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
|
return GetEpisodesPageAsync(tvdbId, 1, episodeQuery, language, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeriesAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
|
||||||
|
var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (imagesSummary.Data.Fanart > 0)
|
||||||
|
{
|
||||||
|
yield return KeyType.Fanart;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesSummary.Data.Series > 0)
|
||||||
|
{
|
||||||
|
yield return KeyType.Series;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesSummary.Data.Poster > 0)
|
||||||
|
{
|
||||||
|
yield return KeyType.Poster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async IAsyncEnumerable<KeyType> GetImageKeyTypesForSeasonAsync(int tvdbId, string language, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var cacheKey = GenerateKey(nameof(TvDbClient.Series.GetImagesSummaryAsync), tvdbId);
|
||||||
|
var imagesSummary = await TryGetValue(cacheKey, language, () => TvDbClient.Series.GetImagesSummaryAsync(tvdbId, cancellationToken)).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (imagesSummary.Data.Season > 0)
|
||||||
|
{
|
||||||
|
yield return KeyType.Season;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imagesSummary.Data.Fanart > 0)
|
||||||
|
{
|
||||||
|
yield return KeyType.Fanart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO seasonwide is not supported in TvDbSharper
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
|
private async Task<T> TryGetValue<T>(string key, string language, Func<Task<T>> resultFactory)
|
||||||
{
|
{
|
||||||
if (_cache.TryGetValue(key, out T cachedValue))
|
if (_cache.TryGetValue(key, out T cachedValue))
|
||||||
|
@ -65,8 +65,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
var language = item.GetPreferredMetadataLanguage();
|
var language = item.GetPreferredMetadataLanguage();
|
||||||
var remoteImages = new List<RemoteImageInfo>();
|
var remoteImages = new List<RemoteImageInfo>();
|
||||||
|
|
||||||
var keyTypes = new[] { KeyType.Season, KeyType.Seasonwide, KeyType.Fanart };
|
var keyTypes = _tvdbClientManager.GetImageKeyTypesForSeasonAsync(tvdbId, language, cancellationToken).ConfigureAwait(false);
|
||||||
foreach (var keyType in keyTypes)
|
await foreach (var keyType in keyTypes)
|
||||||
{
|
{
|
||||||
var imageQuery = new ImagesQuery
|
var imageQuery = new ImagesQuery
|
||||||
{
|
{
|
||||||
|
@ -59,9 +59,10 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
|
|
||||||
var language = item.GetPreferredMetadataLanguage();
|
var language = item.GetPreferredMetadataLanguage();
|
||||||
var remoteImages = new List<RemoteImageInfo>();
|
var remoteImages = new List<RemoteImageInfo>();
|
||||||
var keyTypes = new[] { KeyType.Poster, KeyType.Series, KeyType.Fanart };
|
|
||||||
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
|
var tvdbId = Convert.ToInt32(item.GetProviderId(MetadataProvider.Tvdb));
|
||||||
foreach (KeyType keyType in keyTypes)
|
var allowedKeyTypes = _tvdbClientManager.GetImageKeyTypesForSeriesAsync(tvdbId, language, cancellationToken)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
await foreach (KeyType keyType in allowedKeyTypes)
|
||||||
{
|
{
|
||||||
var imageQuery = new ImagesQuery
|
var imageQuery = new ImagesQuery
|
||||||
{
|
{
|
||||||
|
@ -247,10 +247,15 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
{
|
{
|
||||||
Name = tvdbTitles.FirstOrDefault(),
|
Name = tvdbTitles.FirstOrDefault(),
|
||||||
ProductionYear = firstAired.Year,
|
ProductionYear = firstAired.Year,
|
||||||
SearchProviderName = Name,
|
SearchProviderName = Name
|
||||||
ImageUrl = TvdbUtils.BannerUrl + seriesSearchResult.Banner
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(seriesSearchResult.Banner))
|
||||||
|
{
|
||||||
|
// Results from their Search endpoints already include the /banners/ part in the url, because reasons...
|
||||||
|
remoteSearchResult.ImageUrl = TvdbUtils.TvdbImageBaseUrl + seriesSearchResult.Banner;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var seriesSesult =
|
var seriesSesult =
|
||||||
@ -365,10 +370,14 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
Type = PersonType.Actor,
|
Type = PersonType.Actor,
|
||||||
Name = (actor.Name ?? string.Empty).Trim(),
|
Name = (actor.Name ?? string.Empty).Trim(),
|
||||||
Role = actor.Role,
|
Role = actor.Role,
|
||||||
ImageUrl = TvdbUtils.BannerUrl + actor.Image,
|
|
||||||
SortOrder = actor.SortOrder
|
SortOrder = actor.SortOrder
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(actor.Image))
|
||||||
|
{
|
||||||
|
personInfo.ImageUrl = TvdbUtils.BannerUrl + actor.Image;
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(personInfo.Name))
|
if (!string.IsNullOrWhiteSpace(personInfo.Name))
|
||||||
{
|
{
|
||||||
result.AddPerson(personInfo);
|
result.AddPerson(personInfo);
|
||||||
|
@ -9,7 +9,8 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
|
|||||||
{
|
{
|
||||||
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
|
public const string TvdbApiKey = "OG4V3YJ3FAP7FP2K";
|
||||||
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
|
public const string TvdbBaseUrl = "https://www.thetvdb.com/";
|
||||||
public const string BannerUrl = TvdbBaseUrl + "banners/";
|
public const string TvdbImageBaseUrl = "https://www.thetvdb.com";
|
||||||
|
public const string BannerUrl = TvdbImageBaseUrl + "/banners/";
|
||||||
|
|
||||||
public static ImageType GetImageTypeFromKeyType(string keyType)
|
public static ImageType GetImageTypeFromKeyType(string keyType)
|
||||||
{
|
{
|
||||||
|
@ -58,7 +58,7 @@ namespace MediaBrowser.Providers.TV
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogError(ex, "Error in DummySeasonProvider");
|
Logger.LogError(ex, "Error in DummySeasonProvider for {ItemPath}", item.Path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
6
debian/changelog
vendored
6
debian/changelog
vendored
@ -1,3 +1,9 @@
|
|||||||
|
jellyfin-server (10.6.0-2) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Fix upgrade bug
|
||||||
|
|
||||||
|
-- Joshua Boniface <joshua@boniface.me> Sun, 19 Jul 22:47:27 -0400
|
||||||
|
|
||||||
jellyfin-server (10.6.0-1) unstable; urgency=medium
|
jellyfin-server (10.6.0-1) unstable; urgency=medium
|
||||||
|
|
||||||
* Forthcoming stable release
|
* Forthcoming stable release
|
||||||
|
4
debian/conf/jellyfin
vendored
4
debian/conf/jellyfin
vendored
@ -31,7 +31,7 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
|
|||||||
#JELLYFIN_SERVICE_OPT="--service"
|
#JELLYFIN_SERVICE_OPT="--service"
|
||||||
|
|
||||||
# [OPTIONAL] run Jellyfin without the web app
|
# [OPTIONAL] run Jellyfin without the web app
|
||||||
#JELLYFIN_NOWEBAPP_OPT="--noautorunwebapp"
|
#JELLYFIN_NOWEBAPP_OPT="--nowebclient"
|
||||||
|
|
||||||
#
|
#
|
||||||
# SysV init/Upstart options
|
# SysV init/Upstart options
|
||||||
@ -40,4 +40,4 @@ JELLYFIN_FFMPEG_OPT="--ffmpeg=/usr/lib/jellyfin-ffmpeg/ffmpeg"
|
|||||||
# Application username
|
# Application username
|
||||||
JELLYFIN_USER="jellyfin"
|
JELLYFIN_USER="jellyfin"
|
||||||
# Full application command
|
# Full application command
|
||||||
JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLFIN_NOWEBAPP_OPT"
|
JELLYFIN_ARGS="$JELLYFIN_WEB_OPT $JELLYFIN_RESTART_OPT $JELLYFIN_FFMPEG_OPT $JELLYFIN_SERVICE_OPT $JELLYFIN_NOWEBAPP_OPT"
|
||||||
|
5
debian/control
vendored
5
debian/control
vendored
@ -15,9 +15,8 @@ Vcs-Git: https://github.org/jellyfin/jellyfin.git
|
|||||||
Vcs-Browser: https://github.org/jellyfin/jellyfin
|
Vcs-Browser: https://github.org/jellyfin/jellyfin
|
||||||
|
|
||||||
Package: jellyfin-server
|
Package: jellyfin-server
|
||||||
Replaces: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
|
Replaces: jellyfin (<<10.6.0)
|
||||||
Breaks: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
|
Breaks: jellyfin (<<10.6.0)
|
||||||
Conflicts: mediabrowser, emby, emby-server-beta, jellyfin-dev, emby-server
|
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: at,
|
Depends: at,
|
||||||
libsqlite3-0,
|
libsqlite3-0,
|
||||||
|
@ -8,8 +8,7 @@ set -o xtrace
|
|||||||
# Version variables
|
# Version variables
|
||||||
NSSM_VERSION="nssm-2.24-101-g897c7ad"
|
NSSM_VERSION="nssm-2.24-101-g897c7ad"
|
||||||
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
|
NSSM_URL="http://files.evilt.win/nssm/${NSSM_VERSION}.zip"
|
||||||
FFMPEG_VERSION="ffmpeg-4.3-win64-static"
|
FFMPEG_URL="https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip";
|
||||||
FFMPEG_URL="https://ffmpeg.zeranoe.com/builds/win64/static/${FFMPEG_VERSION}.zip"
|
|
||||||
|
|
||||||
# Move to source directory
|
# Move to source directory
|
||||||
pushd ${SOURCE_DIR}
|
pushd ${SOURCE_DIR}
|
||||||
@ -29,12 +28,11 @@ dotnet publish Jellyfin.Server --configuration Release --self-contained --runtim
|
|||||||
# Prepare addins
|
# Prepare addins
|
||||||
addin_build_dir="$( mktemp -d )"
|
addin_build_dir="$( mktemp -d )"
|
||||||
wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
|
wget ${NSSM_URL} -O ${addin_build_dir}/nssm.zip
|
||||||
wget ${FFMPEG_URL} -O ${addin_build_dir}/ffmpeg.zip
|
wget ${FFMPEG_URL} -O ${addin_build_dir}/jellyfin-ffmpeg.zip
|
||||||
unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
|
unzip ${addin_build_dir}/nssm.zip -d ${addin_build_dir}
|
||||||
cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
|
cp ${addin_build_dir}/${NSSM_VERSION}/win64/nssm.exe ${output_dir}/nssm.exe
|
||||||
unzip ${addin_build_dir}/ffmpeg.zip -d ${addin_build_dir}
|
unzip ${addin_build_dir}/jellyfin-ffmpeg.zip -d ${addin_build_dir}/jellyfin-ffmpeg
|
||||||
cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffmpeg.exe ${output_dir}/ffmpeg.exe
|
cp ${addin_build_dir}/jellyfin-ffmpeg/* ${output_dir}
|
||||||
cp ${addin_build_dir}/${FFMPEG_VERSION}/bin/ffprobe.exe ${output_dir}/ffprobe.exe
|
|
||||||
rm -rf ${addin_build_dir}
|
rm -rf ${addin_build_dir}
|
||||||
|
|
||||||
# Prepare scripts
|
# Prepare scripts
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
<PackageReference Include="AutoFixture" Version="4.13.0" />
|
||||||
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
|
<PackageReference Include="AutoFixture.AutoMoq" Version="4.12.0" />
|
||||||
<PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
|
<PackageReference Include="AutoFixture.Xunit2" Version="4.12.0" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.5" />
|
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="3.1.6" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||||
<PackageReference Include="xunit" Version="2.4.1" />
|
<PackageReference Include="xunit" Version="2.4.1" />
|
||||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1" />
|
||||||
|
@ -47,7 +47,7 @@ function Install-FFMPEG {
|
|||||||
param(
|
param(
|
||||||
[string]$ResolvedInstallLocation,
|
[string]$ResolvedInstallLocation,
|
||||||
[string]$Architecture,
|
[string]$Architecture,
|
||||||
[string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared"
|
[string]$FFMPEGVersionX86 = "ffmpeg-4.3-win32-shared"
|
||||||
)
|
)
|
||||||
Write-Verbose "Checking Architecture"
|
Write-Verbose "Checking Architecture"
|
||||||
if($Architecture -notin @('x86','x64')){
|
if($Architecture -notin @('x86','x64')){
|
||||||
|
Loading…
x
Reference in New Issue
Block a user