mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-15 18:08:53 -07:00
Merge branch 'master' into nullable3
This commit is contained in:
commit
fc049caba2
@ -23,7 +23,7 @@ jobs:
|
||||
NugetPackageName: ${{ Package.value.NugetPackageName }}
|
||||
AssemblyFileName: ${{ Package.value.AssemblyFileName }}
|
||||
maxParallel: 2
|
||||
dependsOn: MainBuild
|
||||
dependsOn: Build
|
||||
steps:
|
||||
- checkout: none
|
||||
|
||||
|
@ -4,15 +4,14 @@ parameters:
|
||||
DotNetSdkVersion: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: MainBuild
|
||||
displayName: Main Build
|
||||
- job: Build
|
||||
displayName: Build
|
||||
strategy:
|
||||
matrix:
|
||||
Release:
|
||||
BuildConfiguration: Release
|
||||
Debug:
|
||||
BuildConfiguration: Debug
|
||||
maxParallel: 2
|
||||
pool:
|
||||
vmImage: "${{ parameters.LinuxImage }}"
|
||||
steps:
|
||||
@ -22,13 +21,13 @@ jobs:
|
||||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
||||
displayName: "Clone Web Branch"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (PR)"
|
||||
displayName: "Clone Web Target"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
@ -37,7 +36,7 @@ jobs:
|
||||
displayName: "Install Node"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), eq(variables['BuildConfiguration'], 'Release'), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
versionSpec: "12.x"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web Client"
|
||||
@ -69,33 +68,33 @@ jobs:
|
||||
command: publish
|
||||
publishWebProjects: false
|
||||
projects: "${{ parameters.RestoreBuildProjects }}"
|
||||
arguments: "--configuration $(BuildConfiguration) --output $(build.artifactstagingdirectory)"
|
||||
arguments: "--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)"
|
||||
zipAfterPublish: false
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Naming"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll"
|
||||
artifactName: "Jellyfin.Naming"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Controller"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll"
|
||||
artifactName: "Jellyfin.Controller"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Model"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll"
|
||||
artifactName: "Jellyfin.Model"
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Common"
|
||||
condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||
targetPath: "$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Common.dll"
|
||||
artifactName: "Jellyfin.Common"
|
||||
|
@ -1,82 +0,0 @@
|
||||
parameters:
|
||||
WindowsImage: "windows-latest"
|
||||
TestProjects: "tests/**/*Tests.csproj"
|
||||
DotNetSdkVersion: 3.1.100
|
||||
|
||||
jobs:
|
||||
- job: PublishWindows
|
||||
displayName: Publish Windows
|
||||
pool:
|
||||
vmImage: ${{ parameters.WindowsImage }}
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
submodules: true
|
||||
persistCredentials: true
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (Master, Release, or Tag)"
|
||||
condition: and(succeeded(), or(contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master'), contains(variables['Build.SourceBranch'], 'tag')), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(Build.SourceBranchName) --depth=1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone Web Client (PR)"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest'))
|
||||
inputs:
|
||||
script: "git clone --single-branch --branch $(System.PullRequest.TargetBranch) --depth 1 https://github.com/jellyfin/jellyfin-web.git $(Agent.TempDirectory)/jellyfin-web"
|
||||
|
||||
- task: NodeTool@0
|
||||
displayName: "Install Node"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
versionSpec: "10.x"
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Build Web Client"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
script: yarn install
|
||||
workingDirectory: $(Agent.TempDirectory)/jellyfin-web
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy Web Client"
|
||||
condition: and(succeeded(), or(contains(variables['System.PullRequest.TargetBranch'], 'release'), contains(variables['System.PullRequest.TargetBranch'], 'master'), contains(variables['Build.SourceBranch'], 'release'), contains(variables['Build.SourceBranch'], 'master')), in(variables['Build.Reason'], 'PullRequest', 'IndividualCI', 'BatchedCI', 'BuildCompletion'))
|
||||
inputs:
|
||||
sourceFolder: $(Agent.TempDirectory)/jellyfin-web/dist
|
||||
contents: "**"
|
||||
targetFolder: $(Build.SourcesDirectory)/MediaBrowser.WebDashboard/jellyfin-web
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: false
|
||||
|
||||
- task: CmdLine@2
|
||||
displayName: "Clone UX Repository"
|
||||
inputs:
|
||||
script: git clone --depth=1 https://github.com/jellyfin/jellyfin-ux $(Agent.TempDirectory)\jellyfin-ux
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: "Build NSIS Installer"
|
||||
inputs:
|
||||
targetType: "filePath"
|
||||
filePath: ./deployment/windows/build-jellyfin.ps1
|
||||
arguments: -InstallFFMPEG -InstallNSSM -MakeNSIS -InstallTrayApp -UXLocation $(Agent.TempDirectory)\jellyfin-ux -InstallLocation $(build.artifactstagingdirectory)
|
||||
errorActionPreference: "stop"
|
||||
workingDirectory: $(Build.SourcesDirectory)
|
||||
|
||||
- task: CopyFiles@2
|
||||
displayName: "Copy NSIS Installer"
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)/deployment/windows/
|
||||
contents: "jellyfin*.exe"
|
||||
targetFolder: $(System.ArtifactsDirectory)/setup
|
||||
cleanTargetFolder: true
|
||||
overWrite: true
|
||||
flattenFolders: true
|
||||
|
||||
- task: PublishPipelineArtifact@0
|
||||
displayName: "Publish Artifact Setup"
|
||||
condition: succeeded()
|
||||
inputs:
|
||||
targetPath: "$(build.artifactstagingdirectory)/setup"
|
||||
artifactName: "Jellyfin Server Setup"
|
@ -27,11 +27,6 @@ jobs:
|
||||
Windows: "windows-latest"
|
||||
macOS: "macos-latest"
|
||||
|
||||
- template: azure-pipelines-windows.yml
|
||||
parameters:
|
||||
WindowsImage: "windows-latest"
|
||||
TestProjects: $(TestProjects)
|
||||
|
||||
- template: azure-pipelines-compat.yml
|
||||
parameters:
|
||||
Packages:
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,6 +39,7 @@ ProgramData*/
|
||||
CorePlugins*/
|
||||
ProgramData-Server*/
|
||||
ProgramData-UI*/
|
||||
MediaBrowser.WebDashboard/jellyfin-web/**
|
||||
|
||||
#################
|
||||
## Visual Studio
|
||||
|
@ -128,6 +128,7 @@
|
||||
- [xosdy](https://github.com/xosdy)
|
||||
- [XVicarious](https://github.com/XVicarious)
|
||||
- [YouKnowBlom](https://github.com/YouKnowBlom)
|
||||
- [KristupasSavickas](https://github.com/KristupasSavickas)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
@ -74,4 +74,4 @@ VOLUME /cache /config /media
|
||||
ENTRYPOINT ["./jellyfin/jellyfin", \
|
||||
"--datadir", "/config", \
|
||||
"--cachedir", "/cache", \
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg"]
|
||||
"--ffmpeg", "/usr/lib/jellyfin-ffmpeg/ffmpeg"]
|
||||
|
@ -505,7 +505,63 @@ namespace Emby.Naming.Common
|
||||
RuleType = ExtraRuleType.Suffix,
|
||||
Token = "-short",
|
||||
MediaType = MediaType.Video
|
||||
}
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.BehindTheScenes,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "behind the scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.DeletedScene,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "deleted scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Interview,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "interviews",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Scene,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "scenes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Sample,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "samples",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "shorts",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Clip,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "featurettes",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
new ExtraRule
|
||||
{
|
||||
ExtraType = ExtraType.Unknown,
|
||||
RuleType = ExtraRuleType.DirectoryName,
|
||||
Token = "extras",
|
||||
MediaType = MediaType.Video,
|
||||
},
|
||||
};
|
||||
|
||||
Format3DRules = new[]
|
||||
|
@ -80,6 +80,15 @@ namespace Emby.Naming.Video
|
||||
result.Rule = rule;
|
||||
}
|
||||
}
|
||||
else if (rule.RuleType == ExtraRuleType.DirectoryName)
|
||||
{
|
||||
var directoryName = Path.GetFileName(Path.GetDirectoryName(path));
|
||||
if (string.Equals(directoryName, rule.Token, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result.ExtraType = rule.ExtraType;
|
||||
result.Rule = rule;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -5,30 +5,29 @@ using MediaType = Emby.Naming.Common.MediaType;
|
||||
|
||||
namespace Emby.Naming.Video
|
||||
{
|
||||
/// <summary>
|
||||
/// A rule used to match a file path with an <see cref="MediaBrowser.Model.Entities.ExtraType"/>.
|
||||
/// </summary>
|
||||
public class ExtraRule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the token.
|
||||
/// Gets or sets the token to use for matching against the file path.
|
||||
/// </summary>
|
||||
/// <value>The token.</value>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the extra.
|
||||
/// Gets or sets the type of the extra to return when matched.
|
||||
/// </summary>
|
||||
/// <value>The type of the extra.</value>
|
||||
public ExtraType ExtraType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the rule.
|
||||
/// </summary>
|
||||
/// <value>The type of the rule.</value>
|
||||
public ExtraRuleType RuleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the media.
|
||||
/// Gets or sets the type of the media to return when matched.
|
||||
/// </summary>
|
||||
/// <value>The type of the media.</value>
|
||||
public MediaType MediaType { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,23 @@ namespace Emby.Naming.Video
|
||||
public enum ExtraRuleType
|
||||
{
|
||||
/// <summary>
|
||||
/// The suffix
|
||||
/// Match <see cref="ExtraRule.Token"/> against a suffix in the file name.
|
||||
/// </summary>
|
||||
Suffix = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The filename
|
||||
/// Match <see cref="ExtraRule.Token"/> against the file name, excluding the file extension.
|
||||
/// </summary>
|
||||
Filename = 1,
|
||||
|
||||
/// <summary>
|
||||
/// The regex
|
||||
/// Match <see cref="ExtraRule.Token"/> against the file name, including the file extension.
|
||||
/// </summary>
|
||||
Regex = 2
|
||||
Regex = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Match <see cref="ExtraRule.Token"/> against the name of the directory containing the file.
|
||||
/// </summary>
|
||||
DirectoryName = 3,
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,6 @@ using Emby.Server.Implementations.Configuration;
|
||||
using Emby.Server.Implementations.Cryptography;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using Emby.Server.Implementations.Devices;
|
||||
using Emby.Server.Implementations.Diagnostics;
|
||||
using Emby.Server.Implementations.Dto;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.HttpServer.Security;
|
||||
@ -86,7 +85,6 @@ using MediaBrowser.MediaEncoding.BdInfo;
|
||||
using MediaBrowser.Model.Activity;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
@ -337,8 +335,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
internal IImageEncoder ImageEncoder { get; private set; }
|
||||
|
||||
protected IProcessFactory ProcessFactory { get; private set; }
|
||||
|
||||
protected readonly IXmlSerializer XmlSerializer;
|
||||
|
||||
protected ISocketFactory SocketFactory { get; private set; }
|
||||
@ -680,9 +676,6 @@ namespace Emby.Server.Implementations
|
||||
|
||||
serviceCollection.AddSingleton(XmlSerializer);
|
||||
|
||||
ProcessFactory = new ProcessFactory();
|
||||
serviceCollection.AddSingleton(ProcessFactory);
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IStreamHelper), typeof(StreamHelper));
|
||||
|
||||
var cryptoProvider = new CryptographyProvider();
|
||||
@ -743,7 +736,6 @@ namespace Emby.Server.Implementations
|
||||
LoggerFactory.CreateLogger<MediaBrowser.MediaEncoding.Encoder.MediaEncoder>(),
|
||||
ServerConfigurationManager,
|
||||
FileSystemManager,
|
||||
ProcessFactory,
|
||||
LocalizationManager,
|
||||
() => SubtitleEncoder,
|
||||
startupConfig,
|
||||
@ -857,8 +849,7 @@ namespace Emby.Server.Implementations
|
||||
FileSystemManager,
|
||||
MediaEncoder,
|
||||
HttpClient,
|
||||
MediaSourceManager,
|
||||
ProcessFactory);
|
||||
MediaSourceManager);
|
||||
serviceCollection.AddSingleton(SubtitleEncoder);
|
||||
|
||||
serviceCollection.AddSingleton(typeof(IResourceFileManager), typeof(ResourceFileManager));
|
||||
@ -1015,48 +1006,12 @@ namespace Emby.Server.Implementations
|
||||
AuthenticatedAttribute.AuthService = AuthService;
|
||||
}
|
||||
|
||||
private async void PluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> args)
|
||||
{
|
||||
string dir = Path.Combine(ApplicationPaths.PluginsPath, args.Argument.name);
|
||||
var types = Directory.EnumerateFiles(dir, "*.dll", SearchOption.AllDirectories)
|
||||
.Select(Assembly.LoadFrom)
|
||||
.SelectMany(x => x.ExportedTypes)
|
||||
.Where(x => x.IsClass && !x.IsAbstract && !x.IsInterface && !x.IsGenericType)
|
||||
.ToArray();
|
||||
|
||||
int oldLen = _allConcreteTypes.Length;
|
||||
Array.Resize(ref _allConcreteTypes, oldLen + types.Length);
|
||||
types.CopyTo(_allConcreteTypes, oldLen);
|
||||
|
||||
var plugins = types.Where(x => x.IsAssignableFrom(typeof(IPlugin)))
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(x => x != null)
|
||||
.Cast<IPlugin>()
|
||||
.Select(LoadPlugin)
|
||||
.Where(x => x != null)
|
||||
.ToArray();
|
||||
|
||||
oldLen = _plugins.Length;
|
||||
Array.Resize(ref _plugins, oldLen + plugins.Length);
|
||||
plugins.CopyTo(_plugins, oldLen);
|
||||
|
||||
var entries = types.Where(x => x.IsAssignableFrom(typeof(IServerEntryPoint)))
|
||||
.Select(CreateInstanceSafe)
|
||||
.Where(x => x != null)
|
||||
.Cast<IServerEntryPoint>()
|
||||
.ToList();
|
||||
|
||||
await Task.WhenAll(StartEntryPoints(entries, true)).ConfigureAwait(false);
|
||||
await Task.WhenAll(StartEntryPoints(entries, false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the parts.
|
||||
/// </summary>
|
||||
public void FindParts()
|
||||
{
|
||||
InstallationManager = ServiceProvider.GetService<IInstallationManager>();
|
||||
InstallationManager.PluginInstalled += PluginInstalled;
|
||||
|
||||
if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
|
||||
{
|
||||
@ -1714,15 +1669,17 @@ namespace Emby.Server.Implementations
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
var process = ProcessFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
FileName = url,
|
||||
EnableRaisingEvents = true,
|
||||
UseShellExecute = true,
|
||||
ErrorDialog = false
|
||||
});
|
||||
|
||||
process.Exited += ProcessExited;
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
process.Exited += (sender, args) => ((Process)sender).Dispose();
|
||||
|
||||
try
|
||||
{
|
||||
@ -1735,11 +1692,6 @@ namespace Emby.Server.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
((IProcess)sender).Dispose();
|
||||
}
|
||||
|
||||
public virtual void EnableLoopback(string appName)
|
||||
{
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Emby.Server.Implementations.HttpServer;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using MediaBrowser.Providers.Music;
|
||||
using static MediaBrowser.Controller.Extensions.ConfigurationExtensions;
|
||||
|
||||
@ -17,6 +18,7 @@ namespace Emby.Server.Implementations
|
||||
{
|
||||
{ HostWebClientKey, bool.TrueString },
|
||||
{ HttpListenerHost.DefaultRedirectKey, "web/index.html" },
|
||||
{ InstallationManager.PluginManifestUrlKey, "https://repo.jellyfin.org/releases/plugin/manifest.json" },
|
||||
{ FfmpegProbeSizeKey, "1G" },
|
||||
{ FfmpegAnalyzeDurationKey, "200M" },
|
||||
{ PlaylistsAllowDuplicatesKey, bool.TrueString }
|
||||
|
@ -287,7 +287,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
}
|
||||
|
||||
public static void TryBind(this IStatement statement, string name, byte[] value)
|
||||
public static void TryBind(this IStatement statement, string name, ReadOnlySpan<byte> value)
|
||||
{
|
||||
if (statement.BindParameters.TryGetValue(name, out IBindParameter bindParam))
|
||||
{
|
||||
|
@ -3315,7 +3315,7 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (!(char.IsLetter(str[i])) && (!(char.IsNumber(str[i]))))
|
||||
if (!char.IsLetter(str[i]) && !char.IsNumber(str[i]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -3339,7 +3339,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return IsAlphaNumeric(value);
|
||||
}
|
||||
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement, string paramSuffix = "")
|
||||
private List<string> GetWhereClauses(InternalItemsQuery query, IStatement statement)
|
||||
{
|
||||
if (query.IsResumable ?? false)
|
||||
{
|
||||
@ -3351,27 +3351,27 @@ namespace Emby.Server.Implementations.Data
|
||||
|
||||
if (query.IsHD.HasValue)
|
||||
{
|
||||
var threshold = 1200;
|
||||
const int Threshold = 1200;
|
||||
if (query.IsHD.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (query.Is4K.HasValue)
|
||||
{
|
||||
var threshold = 3800;
|
||||
const int Threshold = 3800;
|
||||
if (query.Is4K.Value)
|
||||
{
|
||||
minWidth = threshold;
|
||||
minWidth = Threshold;
|
||||
}
|
||||
else
|
||||
{
|
||||
maxWidth = threshold - 1;
|
||||
maxWidth = Threshold - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3380,93 +3380,61 @@ namespace Emby.Server.Implementations.Data
|
||||
if (minWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width>=@MinWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
statement?.TryBind("@MinWidth", minWidth);
|
||||
}
|
||||
|
||||
if (query.MinHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height>=@MinHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
statement?.TryBind("@MinHeight", query.MinHeight);
|
||||
}
|
||||
|
||||
if (maxWidth.HasValue)
|
||||
{
|
||||
whereClauses.Add("Width<=@MaxWidth");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
statement?.TryBind("@MaxWidth", maxWidth);
|
||||
}
|
||||
|
||||
if (query.MaxHeight.HasValue)
|
||||
{
|
||||
whereClauses.Add("Height<=@MaxHeight");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
statement?.TryBind("@MaxHeight", query.MaxHeight);
|
||||
}
|
||||
|
||||
if (query.IsLocked.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsLocked=@IsLocked");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
statement?.TryBind("@IsLocked", query.IsLocked);
|
||||
}
|
||||
|
||||
var tags = query.Tags.ToList();
|
||||
var excludeTags = query.ExcludeTags.ToList();
|
||||
|
||||
if (query.IsMovie ?? false)
|
||||
if (query.IsMovie == true)
|
||||
{
|
||||
var alternateTypes = new List<string>();
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Movie).Name))
|
||||
if (query.IncludeItemTypes.Length == 0
|
||||
|| query.IncludeItemTypes.Contains(nameof(Movie))
|
||||
|| query.IncludeItemTypes.Contains(nameof(Trailer)))
|
||||
{
|
||||
alternateTypes.Add(typeof(Movie).FullName);
|
||||
}
|
||||
if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(typeof(Trailer).Name))
|
||||
{
|
||||
alternateTypes.Add(typeof(Trailer).FullName);
|
||||
}
|
||||
|
||||
var programAttribtues = new List<string>();
|
||||
if (alternateTypes.Count == 0)
|
||||
{
|
||||
programAttribtues.Add("IsMovie=@IsMovie");
|
||||
whereClauses.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
}
|
||||
else
|
||||
{
|
||||
programAttribtues.Add("(IsMovie is null OR IsMovie=@IsMovie)");
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
}
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", true);
|
||||
}
|
||||
|
||||
whereClauses.Add("(" + string.Join(" OR ", programAttribtues) + ")");
|
||||
statement?.TryBind("@IsMovie", true);
|
||||
}
|
||||
else if (query.IsMovie.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsMovie=@IsMovie");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
statement?.TryBind("@IsMovie", query.IsMovie);
|
||||
}
|
||||
|
||||
if (query.IsSeries.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsSeries=@IsSeries");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
statement?.TryBind("@IsSeries", query.IsSeries);
|
||||
}
|
||||
|
||||
if (query.IsSports.HasValue)
|
||||
@ -3518,10 +3486,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsFolder.HasValue)
|
||||
{
|
||||
whereClauses.Add("IsFolder=@IsFolder");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
statement?.TryBind("@IsFolder", query.IsFolder);
|
||||
}
|
||||
|
||||
var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
|
||||
@ -3532,10 +3497,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (excludeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type<>@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", excludeTypes[0]);
|
||||
}
|
||||
else if (excludeTypes.Length > 1)
|
||||
{
|
||||
@ -3546,10 +3508,7 @@ namespace Emby.Server.Implementations.Data
|
||||
else if (includeTypes.Length == 1)
|
||||
{
|
||||
whereClauses.Add("type=@type");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
statement?.TryBind("@type", includeTypes[0]);
|
||||
}
|
||||
else if (includeTypes.Length > 1)
|
||||
{
|
||||
@ -3560,10 +3519,7 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.ChannelIds.Length == 1)
|
||||
{
|
||||
whereClauses.Add("ChannelId=@ChannelId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
statement?.TryBind("@ChannelId", query.ChannelIds[0].ToString("N", CultureInfo.InvariantCulture));
|
||||
}
|
||||
else if (query.ChannelIds.Length > 1)
|
||||
{
|
||||
@ -3574,98 +3530,65 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!query.ParentId.Equals(Guid.Empty))
|
||||
{
|
||||
whereClauses.Add("ParentId=@ParentId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
statement?.TryBind("@ParentId", query.ParentId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Path))
|
||||
{
|
||||
whereClauses.Add("Path=@Path");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
statement?.TryBind("@Path", GetPathToSave(query.Path));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.PresentationUniqueKey))
|
||||
{
|
||||
whereClauses.Add("PresentationUniqueKey=@PresentationUniqueKey");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
statement?.TryBind("@PresentationUniqueKey", query.PresentationUniqueKey);
|
||||
}
|
||||
|
||||
if (query.MinCommunityRating.HasValue)
|
||||
{
|
||||
whereClauses.Add("CommunityRating>=@MinCommunityRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
statement?.TryBind("@MinCommunityRating", query.MinCommunityRating.Value);
|
||||
}
|
||||
|
||||
if (query.MinIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber>=@MinIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@MinIndexNumber", query.MinIndexNumber.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateCreated.HasValue)
|
||||
{
|
||||
whereClauses.Add("DateCreated>=@MinDateCreated");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateCreated", query.MinDateCreated.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSaved.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSaved", query.MinDateLastSaved.Value);
|
||||
}
|
||||
|
||||
if (query.MinDateLastSavedForUser.HasValue)
|
||||
{
|
||||
whereClauses.Add("(DateLastSaved not null and DateLastSaved>=@MinDateLastSavedForUser)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
statement?.TryBind("@MinDateLastSavedForUser", query.MinDateLastSavedForUser.Value);
|
||||
}
|
||||
|
||||
if (query.IndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("IndexNumber=@IndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@IndexNumber", query.IndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumber.HasValue)
|
||||
{
|
||||
whereClauses.Add("ParentIndexNumber=@ParentIndexNumber");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumber", query.ParentIndexNumber.Value);
|
||||
}
|
||||
if (query.ParentIndexNumberNotEquals.HasValue)
|
||||
{
|
||||
whereClauses.Add("(ParentIndexNumber<>@ParentIndexNumberNotEquals or ParentIndexNumber is null)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
statement?.TryBind("@ParentIndexNumberNotEquals", query.ParentIndexNumberNotEquals.Value);
|
||||
}
|
||||
|
||||
var minEndDate = query.MinEndDate;
|
||||
@ -3686,73 +3609,59 @@ namespace Emby.Server.Implementations.Data
|
||||
if (minEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", minEndDate.Value);
|
||||
}
|
||||
|
||||
if (maxEndDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("EndDate<=@MaxEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxEndDate", maxEndDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate>=@MinStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinStartDate", query.MinStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxStartDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", query.MaxStartDate.Value);
|
||||
}
|
||||
|
||||
if (query.MinPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate>=@MinPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MinPremiereDate", query.MinPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.MaxPremiereDate.HasValue)
|
||||
{
|
||||
whereClauses.Add("PremiereDate<=@MaxPremiereDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
statement?.TryBind("@MaxPremiereDate", query.MaxPremiereDate.Value);
|
||||
}
|
||||
|
||||
if (query.TrailerTypes.Length > 0)
|
||||
var trailerTypes = query.TrailerTypes;
|
||||
int trailerTypesLen = trailerTypes.Length;
|
||||
if (trailerTypesLen > 0)
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var type in query.TrailerTypes)
|
||||
const string Or = " OR ";
|
||||
StringBuilder clause = new StringBuilder("(", trailerTypesLen * 32);
|
||||
for (int i = 0; i < trailerTypesLen; i++)
|
||||
{
|
||||
var paramName = "@TrailerTypes" + index;
|
||||
|
||||
clauses.Add("TrailerTypes like " + paramName);
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, "%" + type + "%");
|
||||
}
|
||||
index++;
|
||||
var paramName = "@TrailerTypes" + i;
|
||||
clause.Append("TrailerTypes like ")
|
||||
.Append(paramName)
|
||||
.Append(Or);
|
||||
statement?.TryBind(paramName, "%" + trailerTypes[i] + "%");
|
||||
}
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
|
||||
// Remove last " OR "
|
||||
clause.Length -= Or.Length;
|
||||
clause.Append(')');
|
||||
|
||||
whereClauses.Add(clause.ToString());
|
||||
}
|
||||
|
||||
if (query.IsAiring.HasValue)
|
||||
@ -3760,24 +3669,15 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsAiring.Value)
|
||||
{
|
||||
whereClauses.Add("StartDate<=@MaxStartDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MaxStartDate", DateTime.UtcNow);
|
||||
|
||||
whereClauses.Add("EndDate>=@MinEndDate");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@MinEndDate", DateTime.UtcNow);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
statement?.TryBind("@IsAiringDate", DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3792,13 +3692,10 @@ namespace Emby.Server.Implementations.Data
|
||||
var paramName = "@PersonId" + index;
|
||||
|
||||
clauses.Add("(guid in (select itemid from People where Name = (select Name from TypedBaseItems where guid=" + paramName + ")))");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind(paramName, personId.ToByteArray());
|
||||
}
|
||||
statement?.TryBind(paramName, personId.ToByteArray());
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@ -3806,47 +3703,31 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!string.IsNullOrWhiteSpace(query.Person))
|
||||
{
|
||||
whereClauses.Add("Guid in (select ItemId from People where Name=@PersonName)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
statement?.TryBind("@PersonName", query.Person);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.MinSortName))
|
||||
{
|
||||
whereClauses.Add("SortName>=@MinSortName");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
statement?.TryBind("@MinSortName", query.MinSortName);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalSeriesId))
|
||||
{
|
||||
whereClauses.Add("ExternalSeriesId=@ExternalSeriesId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
statement?.TryBind("@ExternalSeriesId", query.ExternalSeriesId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.ExternalId))
|
||||
{
|
||||
whereClauses.Add("ExternalId=@ExternalId");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
statement?.TryBind("@ExternalId", query.ExternalId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.Name))
|
||||
{
|
||||
whereClauses.Add("CleanName=@Name");
|
||||
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
statement?.TryBind("@Name", GetCleanValue(query.Name));
|
||||
}
|
||||
|
||||
// These are the same, for now
|
||||
@ -3865,28 +3746,21 @@ namespace Emby.Server.Implementations.Data
|
||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWith))
|
||||
{
|
||||
whereClauses.Add("SortName like @NameStartsWith");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
statement?.TryBind("@NameStartsWith", query.NameStartsWith + "%");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameStartsWithOrGreater))
|
||||
{
|
||||
whereClauses.Add("SortName >= @NameStartsWithOrGreater");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameStartsWithOrGreater", query.NameStartsWithOrGreater.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.NameLessThan))
|
||||
{
|
||||
whereClauses.Add("SortName < @NameLessThan");
|
||||
// lowercase this because SortName is stored as lowercase
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
statement?.TryBind("@NameLessThan", query.NameLessThan.ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (query.ImageTypes.Length > 0)
|
||||
@ -3902,18 +3776,12 @@ namespace Emby.Server.Implementations.Data
|
||||
if (query.IsLiked.Value)
|
||||
{
|
||||
whereClauses.Add("rating>=@UserRating");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
whereClauses.Add("(rating is null or rating<@UserRating)");
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
statement?.TryBind("@UserRating", UserItemData.MinLikeValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3927,10 +3795,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavoriteOrLiked)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavoriteOrLiked", query.IsFavoriteOrLiked.Value);
|
||||
}
|
||||
|
||||
if (query.IsFavorite.HasValue)
|
||||
@ -3943,10 +3809,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(IsFavorite is null or IsFavorite=@IsFavorite)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsFavorite", query.IsFavorite.Value);
|
||||
}
|
||||
|
||||
if (EnableJoinUserData(query))
|
||||
@ -3975,10 +3839,8 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
whereClauses.Add("(played is null or played=@IsPlayed)");
|
||||
}
|
||||
if (statement != null)
|
||||
{
|
||||
statement.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
|
||||
statement?.TryBind("@IsPlayed", query.IsPlayed.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4010,6 +3872,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@ -4029,6 +3892,7 @@ namespace Emby.Server.Implementations.Data
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
var clause = "(" + string.Join(" OR ", clauses) + ")";
|
||||
whereClauses.Add(clause);
|
||||
}
|
||||
@ -4762,18 +4626,22 @@ namespace Emby.Server.Implementations.Data
|
||||
{
|
||||
list.Add(typeof(Person).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Genre).Name, query))
|
||||
{
|
||||
list.Add(typeof(Genre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicGenre).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicGenre).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(MusicArtist).Name, query))
|
||||
{
|
||||
list.Add(typeof(MusicArtist).Name);
|
||||
}
|
||||
|
||||
if (IsTypeInQuery(typeof(Studio).Name, query))
|
||||
{
|
||||
list.Add(typeof(Studio).Name);
|
||||
@ -4847,7 +4715,7 @@ namespace Emby.Server.Implementations.Data
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly Type[] KnownTypes =
|
||||
private static readonly Type[] _knownTypes =
|
||||
{
|
||||
typeof(LiveTvProgram),
|
||||
typeof(LiveTvChannel),
|
||||
@ -4916,7 +4784,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
var dict = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var t in KnownTypes)
|
||||
foreach (var t in _knownTypes)
|
||||
{
|
||||
dict[t.Name] = new[] { t.FullName };
|
||||
}
|
||||
@ -4928,7 +4796,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
// Not crazy about having this all the way down here, but at least it's in one place
|
||||
readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
private readonly Dictionary<string, string[]> _types = GetTypeMapDictionary();
|
||||
|
||||
private string[] MapIncludeItemTypes(string value)
|
||||
{
|
||||
@ -4945,7 +4813,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void DeleteItem(Guid id, CancellationToken cancellationToken)
|
||||
public void DeleteItem(Guid id)
|
||||
{
|
||||
if (id == Guid.Empty)
|
||||
{
|
||||
@ -4981,7 +4849,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, byte[] value)
|
||||
private void ExecuteWithSingleParam(IDatabaseConnection db, string query, ReadOnlySpan<byte> value)
|
||||
{
|
||||
using (var statement = PrepareStatement(db, query))
|
||||
{
|
||||
@ -5541,6 +5409,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
{
|
||||
GetWhereClauses(typeSubQuery, null);
|
||||
}
|
||||
|
||||
BindSimilarParams(query, statement);
|
||||
BindSearchParams(query, statement);
|
||||
GetWhereClauses(innerQuery, statement);
|
||||
@ -5582,7 +5451,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
}
|
||||
|
||||
var allTypes = typeString.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.ToLookup(i => i);
|
||||
.ToLookup(x => x);
|
||||
|
||||
foreach (var type in allTypes)
|
||||
{
|
||||
@ -5673,30 +5542,26 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
|
||||
while (startIndex < values.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into ItemValues (ItemId, Type, Value, CleanValue) values ");
|
||||
|
||||
var endIndex = Math.Min(values.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
var endIndex = Math.Min(values.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat(
|
||||
CultureInfo.InvariantCulture,
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0})",
|
||||
"(@ItemId, @Type{0}, @Value{0}, @CleanValue{0}),",
|
||||
i);
|
||||
isSubsequentRow = true;
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
@ -5724,7 +5589,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5759,28 +5624,23 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertPeople(byte[] idBlob, List<PersonInfo> people, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 100;
|
||||
var startIndex = 0;
|
||||
var limit = 100;
|
||||
var listIndex = 0;
|
||||
|
||||
while (startIndex < people.Count)
|
||||
{
|
||||
var insertText = new StringBuilder("insert into People (ItemId, Name, Role, PersonType, SortOrder, ListOrder) values ");
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + limit);
|
||||
var isSubsequentRow = false;
|
||||
|
||||
var endIndex = Math.Min(people.Count, startIndex + Limit);
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (isSubsequentRow)
|
||||
{
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0})", i.ToString(CultureInfo.InvariantCulture));
|
||||
isSubsequentRow = true;
|
||||
insertText.AppendFormat("(@ItemId, @Name{0}, @Role{0}, @PersonType{0}, @SortOrder{0}, @ListOrder{0}),", i.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
{
|
||||
statement.TryBind("@ItemId", idBlob);
|
||||
@ -5804,16 +5664,17 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
private PersonInfo GetPerson(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var item = new PersonInfo();
|
||||
|
||||
item.ItemId = reader.GetGuid(0);
|
||||
item.Name = reader.GetString(1);
|
||||
var item = new PersonInfo
|
||||
{
|
||||
ItemId = reader.GetGuid(0),
|
||||
Name = reader.GetString(1)
|
||||
};
|
||||
|
||||
if (!reader.IsDBNull(2))
|
||||
{
|
||||
@ -5920,20 +5781,28 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
private void InsertMediaStreams(byte[] idBlob, List<MediaStream> streams, IDatabaseConnection db)
|
||||
{
|
||||
const int Limit = 10;
|
||||
var startIndex = 0;
|
||||
var limit = 10;
|
||||
|
||||
while (startIndex < streams.Count)
|
||||
{
|
||||
var insertText = new StringBuilder(string.Format("insert into mediastreams ({0}) values ", string.Join(",", _mediaStreamSaveColumns)));
|
||||
var insertText = new StringBuilder("insert into mediastreams (");
|
||||
foreach (var column in _mediaStreamSaveColumns)
|
||||
{
|
||||
insertText.Append(column).Append(',');
|
||||
}
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + limit);
|
||||
// Remove last comma
|
||||
insertText.Length--;
|
||||
insertText.Append(") values ");
|
||||
|
||||
var endIndex = Math.Min(streams.Count, startIndex + Limit);
|
||||
|
||||
for (var i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (i != startIndex)
|
||||
{
|
||||
insertText.Append(",");
|
||||
insertText.Append(',');
|
||||
}
|
||||
|
||||
var index = i.ToString(CultureInfo.InvariantCulture);
|
||||
@ -5941,11 +5810,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
|
||||
foreach (var column in _mediaStreamSaveColumns.Skip(1))
|
||||
{
|
||||
insertText.Append("@" + column + index + ",");
|
||||
insertText.Append('@').Append(column).Append(index).Append(',');
|
||||
}
|
||||
|
||||
insertText.Length -= 1; // Remove the last comma
|
||||
|
||||
insertText.Append(")");
|
||||
insertText.Append(')');
|
||||
}
|
||||
|
||||
using (var statement = PrepareStatement(db, insertText.ToString()))
|
||||
@ -6007,7 +5877,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
statement.MoveNext();
|
||||
}
|
||||
|
||||
startIndex += limit;
|
||||
startIndex += Limit;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6024,7 +5894,7 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
|
||||
Index = reader[1].ToInt()
|
||||
};
|
||||
|
||||
item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader[2].ToString(), true);
|
||||
item.Type = Enum.Parse<MediaStreamType>(reader[2].ToString(), true);
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
|
@ -1,152 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
|
||||
namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class CommonProcess : IProcess
|
||||
{
|
||||
private readonly Process _process;
|
||||
|
||||
private bool _disposed = false;
|
||||
private bool _hasExited;
|
||||
|
||||
public CommonProcess(ProcessOptions options)
|
||||
{
|
||||
StartInfo = options;
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = options.Arguments,
|
||||
FileName = options.FileName,
|
||||
WorkingDirectory = options.WorkingDirectory,
|
||||
UseShellExecute = options.UseShellExecute,
|
||||
CreateNoWindow = options.CreateNoWindow,
|
||||
RedirectStandardError = options.RedirectStandardError,
|
||||
RedirectStandardInput = options.RedirectStandardInput,
|
||||
RedirectStandardOutput = options.RedirectStandardOutput,
|
||||
ErrorDialog = options.ErrorDialog
|
||||
};
|
||||
|
||||
|
||||
if (options.IsHidden)
|
||||
{
|
||||
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
}
|
||||
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
|
||||
if (options.EnableRaisingEvents)
|
||||
{
|
||||
_process.EnableRaisingEvents = true;
|
||||
_process.Exited += OnProcessExited;
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler Exited;
|
||||
|
||||
public ProcessOptions StartInfo { get; }
|
||||
|
||||
public StreamWriter StandardInput => _process.StandardInput;
|
||||
|
||||
public StreamReader StandardError => _process.StandardError;
|
||||
|
||||
public StreamReader StandardOutput => _process.StandardOutput;
|
||||
|
||||
public int ExitCode => _process.ExitCode;
|
||||
|
||||
private bool HasExited
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasExited)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_hasExited = _process.HasExited;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
_hasExited = true;
|
||||
}
|
||||
|
||||
return _hasExited;
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_process.Start();
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
{
|
||||
_process.Kill();
|
||||
}
|
||||
|
||||
public bool WaitForExit(int timeMs)
|
||||
{
|
||||
return _process.WaitForExit(timeMs);
|
||||
}
|
||||
|
||||
public Task<bool> WaitForExitAsync(int timeMs)
|
||||
{
|
||||
// Note: For this function to work correctly, the option EnableRisingEvents needs to be set to true.
|
||||
|
||||
if (HasExited)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
timeMs = Math.Max(0, timeMs);
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
var cancellationToken = new CancellationTokenSource(timeMs).Token;
|
||||
|
||||
_process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||
|
||||
cancellationToken.Register(() => tcs.TrySetResult(HasExited));
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_process?.Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void OnProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
_hasExited = true;
|
||||
Exited?.Invoke(this, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
|
||||
namespace Emby.Server.Implementations.Diagnostics
|
||||
{
|
||||
public class ProcessFactory : IProcessFactory
|
||||
{
|
||||
public IProcess Create(ProcessOptions options)
|
||||
{
|
||||
return new CommonProcess(options);
|
||||
}
|
||||
}
|
||||
}
|
@ -1056,30 +1056,19 @@ namespace Emby.Server.Implementations.Dto
|
||||
|
||||
if (options.ContainsField(ItemFields.SpecialFeatureCount))
|
||||
{
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
dto.SpecialFeatureCount = allExtras.Count(i => i.ExtraType.HasValue && BaseItem.DisplayExtraTypes.Contains(i.ExtraType.Value));
|
||||
}
|
||||
|
||||
if (options.ContainsField(ItemFields.LocalTrailerCount))
|
||||
{
|
||||
int trailerCount = 0;
|
||||
if (allExtras == null)
|
||||
{
|
||||
allExtras = item.GetExtras().ToArray();
|
||||
}
|
||||
|
||||
trailerCount += allExtras.Count(i => i.ExtraType.HasValue && i.ExtraType.Value == ExtraType.Trailer);
|
||||
allExtras ??= item.GetExtras().ToArray();
|
||||
dto.LocalTrailerCount = allExtras.Count(i => i.ExtraType == ExtraType.Trailer);
|
||||
|
||||
if (item is IHasTrailers hasTrailers)
|
||||
{
|
||||
trailerCount += hasTrailers.GetTrailerCount();
|
||||
dto.LocalTrailerCount += hasTrailers.GetTrailerCount();
|
||||
}
|
||||
|
||||
dto.LocalTrailerCount = trailerCount;
|
||||
}
|
||||
|
||||
// Add EpisodeInfo
|
||||
|
@ -32,11 +32,11 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="Mono.Nat" Version="2.0.0" />
|
||||
<PackageReference Include="ServiceStack.Text.Core" Version="5.8.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.24.0" />
|
||||
<PackageReference Include="sharpcompress" Version="0.25.0" />
|
||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.1.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="4.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -23,6 +23,7 @@ using MediaBrowser.Model.Services;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ServiceStack.Text.Jsv;
|
||||
|
||||
@ -48,6 +49,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
private readonly string _baseUrlPrefix;
|
||||
private readonly Dictionary<Type, Type> _serviceOperationsMap = new Dictionary<Type, Type>();
|
||||
private readonly List<IWebSocketConnection> _webSocketConnections = new List<IWebSocketConnection>();
|
||||
private readonly IHostEnvironment _hostEnvironment;
|
||||
|
||||
private IWebSocketListener[] _webSocketListeners = Array.Empty<IWebSocketListener>();
|
||||
private bool _disposed = false;
|
||||
|
||||
@ -61,7 +64,8 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
IXmlSerializer xmlSerializer,
|
||||
IHttpListener socketListener,
|
||||
ILocalizationManager localizationManager,
|
||||
ServiceController serviceController)
|
||||
ServiceController serviceController,
|
||||
IHostEnvironment hostEnvironment)
|
||||
{
|
||||
_appHost = applicationHost;
|
||||
_logger = logger;
|
||||
@ -75,6 +79,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
ServiceController = serviceController;
|
||||
|
||||
_socketListener.WebSocketConnected = OnWebSocketConnected;
|
||||
_hostEnvironment = hostEnvironment;
|
||||
|
||||
_funcParseFn = t => s => JsvReader.GetParseFn(t)(s);
|
||||
|
||||
@ -234,7 +239,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace)
|
||||
private async Task ErrorHandler(Exception ex, IRequest httpReq, bool logExceptionStackTrace, string urlToLog)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -242,11 +247,11 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
|
||||
if (logExceptionStackTrace)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing request");
|
||||
_logger.LogError(ex, "Error processing request. URL: {Url}", urlToLog);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError("Error processing request: {Message}", ex.Message);
|
||||
_logger.LogError("Error processing request: {Message}. URL: {Url}", ex.Message.TrimEnd('.'), urlToLog);
|
||||
}
|
||||
|
||||
var httpRes = httpReq.Response;
|
||||
@ -266,7 +271,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
catch (Exception errorEx)
|
||||
{
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response)");
|
||||
_logger.LogError(errorEx, "Error this.ProcessRequest(context)(Exception while writing error to the response). URL: {Url}", urlToLog);
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,7 +456,7 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
var stopWatch = new Stopwatch();
|
||||
stopWatch.Start();
|
||||
var httpRes = httpReq.Response;
|
||||
string urlToLog = null;
|
||||
string urlToLog = GetUrlToLog(urlString);
|
||||
string remoteIp = httpReq.RemoteIp;
|
||||
|
||||
try
|
||||
@ -497,8 +502,6 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
return;
|
||||
}
|
||||
|
||||
urlToLog = GetUrlToLog(urlString);
|
||||
|
||||
if (string.Equals(localPath, _baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, _baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
|
||||
@ -530,22 +533,25 @@ namespace Emby.Server.Implementations.HttpServer
|
||||
}
|
||||
else
|
||||
{
|
||||
await ErrorHandler(new FileNotFoundException(), httpReq, false).ConfigureAwait(false);
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is SocketException || ex is IOException || ex is OperationCanceledException)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
await ErrorHandler(ex, httpReq, false).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logException = !string.Equals(ex.GetType().Name, "SocketException", StringComparison.OrdinalIgnoreCase);
|
||||
// Do not handle exceptions manually when in development mode
|
||||
// The framework-defined development exception page will be returned instead
|
||||
if (_hostEnvironment.IsDevelopment())
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
await ErrorHandler(ex, httpReq, logException).ConfigureAwait(false);
|
||||
bool ignoreStackTrace =
|
||||
ex is SocketException
|
||||
|| ex is IOException
|
||||
|| ex is OperationCanceledException
|
||||
|| ex is SecurityException
|
||||
|| ex is FileNotFoundException;
|
||||
await ErrorHandler(ex, httpReq, !ignoreStackTrace, urlToLog).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -3,33 +3,38 @@ namespace Emby.Server.Implementations
|
||||
public interface IStartupOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// --ffmpeg
|
||||
/// Gets the value of the --ffmpeg command line option.
|
||||
/// </summary>
|
||||
string FFmpegPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --service
|
||||
/// Gets the value of the --service command line option.
|
||||
/// </summary>
|
||||
bool IsService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --noautorunwebapp
|
||||
/// Gets the value of the --noautorunwebapp command line option.
|
||||
/// </summary>
|
||||
bool NoAutoRunWebApp { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --package-name
|
||||
/// Gets the value of the --package-name command line option.
|
||||
/// </summary>
|
||||
string PackageName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartpath
|
||||
/// Gets the value of the --restartpath command line option.
|
||||
/// </summary>
|
||||
string RestartPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// --restartargs
|
||||
/// Gets the value of the --restartargs command line option.
|
||||
/// </summary>
|
||||
string RestartArgs { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the --plugin-manifest-url command line option.
|
||||
/// </summary>
|
||||
string PluginManifestUrl { get; }
|
||||
}
|
||||
}
|
||||
|
@ -437,10 +437,10 @@ namespace Emby.Server.Implementations.Library
|
||||
|
||||
item.SetParent(null);
|
||||
|
||||
ItemRepository.DeleteItem(item.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(item.Id);
|
||||
foreach (var child in children)
|
||||
{
|
||||
ItemRepository.DeleteItem(child.Id, CancellationToken.None);
|
||||
ItemRepository.DeleteItem(child.Id);
|
||||
}
|
||||
|
||||
_libraryItemsCache.TryRemove(item.Id, out BaseItem removed);
|
||||
@ -2609,14 +2609,12 @@ namespace Emby.Server.Implementations.Library
|
||||
}).OrderBy(i => i.Path);
|
||||
}
|
||||
|
||||
private static readonly string[] ExtrasSubfolderNames = new[] { "extras", "specials", "shorts", "scenes", "featurettes", "behind the scenes", "deleted scenes", "interviews" };
|
||||
|
||||
public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
|
||||
{
|
||||
var namingOptions = GetNamingOptions();
|
||||
|
||||
var files = owner.IsInMixedFolder ? new List<FileSystemMetadata>() : fileSystemChildren.Where(i => i.IsDirectory)
|
||||
.Where(i => ExtrasSubfolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.Where(i => BaseItem.AllExtrasTypesFolderNames.Contains(i.Name ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
.SelectMany(i => _fileSystem.GetFiles(i.FullName, _videoFileExtensions, false, false))
|
||||
.ToList();
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -25,7 +26,6 @@ using MediaBrowser.Controller.LiveTv;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Events;
|
||||
@ -61,7 +61,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IProviderManager _providerManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IStreamHelper _streamHelper;
|
||||
|
||||
@ -88,8 +87,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
ILibraryManager libraryManager,
|
||||
ILibraryMonitor libraryMonitor,
|
||||
IProviderManager providerManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IProcessFactory processFactory)
|
||||
IMediaEncoder mediaEncoder)
|
||||
{
|
||||
Current = this;
|
||||
|
||||
@ -102,7 +100,6 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_libraryMonitor = libraryMonitor;
|
||||
_providerManager = providerManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_processFactory = processFactory;
|
||||
_liveTvManager = (LiveTvManager)liveTvManager;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
@ -1662,7 +1659,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
{
|
||||
if (mediaSource.RequiresLooping || !(mediaSource.Container ?? string.Empty).EndsWith("ts", StringComparison.OrdinalIgnoreCase) || (mediaSource.Protocol != MediaProtocol.File && mediaSource.Protocol != MediaProtocol.Http))
|
||||
{
|
||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _processFactory, _config);
|
||||
return new EncodedRecorder(_logger, _mediaEncoder, _config.ApplicationPaths, _jsonSerializer, _config);
|
||||
}
|
||||
|
||||
return new DirectRecorder(_logger, _httpClient, _streamHelper);
|
||||
@ -1683,16 +1680,19 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
try
|
||||
{
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||
CreateNoWindow = true,
|
||||
EnableRaisingEvents = true,
|
||||
ErrorDialog = false,
|
||||
FileName = options.RecordingPostProcessor,
|
||||
IsHidden = true,
|
||||
UseShellExecute = false
|
||||
});
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = GetPostProcessArguments(path, options.RecordingPostProcessorArguments),
|
||||
CreateNoWindow = true,
|
||||
ErrorDialog = false,
|
||||
FileName = options.RecordingPostProcessor,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
UseShellExecute = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
|
||||
_logger.LogInformation("Running recording post processor {0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
@ -1712,11 +1712,9 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
private void Process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
using (var process = (IProcess)sender)
|
||||
using (var process = (Process)sender)
|
||||
{
|
||||
_logger.LogInformation("Recording post-processing script completed with exit code {ExitCode}", process.ExitCode);
|
||||
|
||||
process.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@ -13,7 +14,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
@ -29,8 +29,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
private bool _hasExited;
|
||||
private Stream _logFileStream;
|
||||
private string _targetPath;
|
||||
private IProcess _process;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private Process _process;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly TaskCompletionSource<bool> _taskCompletionSource = new TaskCompletionSource<bool>();
|
||||
private readonly IServerConfigurationManager _config;
|
||||
@ -40,14 +39,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
IMediaEncoder mediaEncoder,
|
||||
IServerApplicationPaths appPaths,
|
||||
IJsonSerializer json,
|
||||
IProcessFactory processFactory,
|
||||
IServerConfigurationManager config)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
_processFactory = processFactory;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
@ -79,7 +76,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
_targetPath = targetFile;
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
@ -90,14 +87,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
|
||||
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
EnableRaisingEvents = true
|
||||
});
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
};
|
||||
|
||||
_process = process;
|
||||
|
||||
var commandLineLogMessage = process.StartInfo.FileName + " " + process.StartInfo.Arguments;
|
||||
var commandLineLogMessage = processStartInfo.FileName + " " + processStartInfo.Arguments;
|
||||
_logger.LogInformation(commandLineLogMessage);
|
||||
|
||||
var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "record-transcode-" + Guid.NewGuid() + ".txt");
|
||||
@ -109,16 +103,21 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
|
||||
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
|
||||
|
||||
process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile);
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = processStartInfo,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
_process.Exited += (sender, args) => OnFfMpegProcessExited(_process, inputFile);
|
||||
|
||||
process.Start();
|
||||
_process.Start();
|
||||
|
||||
cancellationToken.Register(Stop);
|
||||
|
||||
onStarted();
|
||||
|
||||
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
|
||||
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
|
||||
StartStreamingLog(_process.StandardError.BaseStream, _logFileStream);
|
||||
|
||||
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
|
||||
|
||||
@ -292,30 +291,33 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
/// <summary>
|
||||
/// Processes the exited.
|
||||
/// </summary>
|
||||
private void OnFfMpegProcessExited(IProcess process, string inputFile)
|
||||
private void OnFfMpegProcessExited(Process process, string inputFile)
|
||||
{
|
||||
_hasExited = true;
|
||||
|
||||
_logFileStream?.Dispose();
|
||||
_logFileStream = null;
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
|
||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||
|
||||
if (exitCode == 0)
|
||||
using (process)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskCompletionSource.TrySetException(
|
||||
new Exception(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Recording for {0} failed. Exit code {1}",
|
||||
_targetPath,
|
||||
exitCode)));
|
||||
_hasExited = true;
|
||||
|
||||
_logFileStream?.Dispose();
|
||||
_logFileStream = null;
|
||||
|
||||
var exitCode = process.ExitCode;
|
||||
|
||||
_logger.LogInformation("FFMpeg recording exited with code {ExitCode} for {Path}", exitCode, _targetPath);
|
||||
|
||||
if (exitCode == 0)
|
||||
{
|
||||
_taskCompletionSource.TrySetResult(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_taskCompletionSource.TrySetException(
|
||||
new Exception(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Recording for {0} failed. Exit code {1}",
|
||||
_targetPath,
|
||||
exitCode)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,5 +102,17 @@
|
||||
"TaskRefreshLibrary": "افحص مكتبة الوسائط",
|
||||
"TaskRefreshChapterImagesDescription": "إنشاء صور مصغرة لمقاطع الفيديو ذات فصول.",
|
||||
"TaskRefreshChapterImages": "استخراج صور الفصل",
|
||||
"TasksApplicationCategory": "تطبيق"
|
||||
"TasksApplicationCategory": "تطبيق",
|
||||
"TaskDownloadMissingSubtitlesDescription": "ابحث في الإنترنت على الترجمات المفقودة إستنادا على الميتاداتا.",
|
||||
"TaskDownloadMissingSubtitles": "تحميل الترجمات المفقودة",
|
||||
"TaskRefreshChannelsDescription": "تحديث معلومات قنوات الإنترنت.",
|
||||
"TaskRefreshChannels": "إعادة تحديث القنوات",
|
||||
"TaskCleanTranscodeDescription": "حذف ملفات الترميز الأقدم من يوم واحد.",
|
||||
"TaskCleanTranscode": "حذف سجلات الترميز",
|
||||
"TaskUpdatePluginsDescription": "تحميل وتثبيت الإضافات التي تم تفعيل التحديث التلقائي لها.",
|
||||
"TaskUpdatePlugins": "تحديث الإضافات",
|
||||
"TaskRefreshPeopleDescription": "تحديث البيانات الوصفية للممثلين والمخرجين في مكتبة الوسائط الخاصة بك.",
|
||||
"TaskRefreshPeople": "إعادة تحميل الأشخاص",
|
||||
"TaskCleanLogsDescription": "حذف السجلات الأقدم من {0} يوم.",
|
||||
"TaskCleanLogs": "حذف دليل السجل"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"Albums": "Album",
|
||||
"Albums": "Albums",
|
||||
"AppDeviceValues": "App: {0}, Enhed: {1}",
|
||||
"Application": "Applikation",
|
||||
"Artists": "Kunstnere",
|
||||
@ -92,5 +92,21 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} har afsluttet afspilning af {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} er blevet tilføjet til dit mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Søger på internettet efter manglende undertekster baseret på metadata konfiration.",
|
||||
"TaskDownloadMissingSubtitles": "Download manglende undertekster",
|
||||
"TaskUpdatePluginsDescription": "Downloader og installere opdateringer for plugins som er konfigureret til at opdatere automatisk.",
|
||||
"TaskUpdatePlugins": "Opdater Plugins",
|
||||
"TaskCleanLogsDescription": "Sletter log filer som er mere end {0} dage gammle.",
|
||||
"TaskCleanLogs": "Ryd Log Mappe",
|
||||
"TaskRefreshLibraryDescription": "Scanner dit medie bibliotek for nye filer og opdatere metadata.",
|
||||
"TaskRefreshLibrary": "Scan Medie Bibliotek",
|
||||
"TaskCleanCacheDescription": "Sletter cache filer som systemet ikke har brug for længere.",
|
||||
"TaskCleanCache": "Ryd Cache Mappe",
|
||||
"TasksChannelsCategory": "Internet Kanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Vedligeholdelse",
|
||||
"TaskRefreshChapterImages": "Udtræk Kapitel billeder",
|
||||
"TaskRefreshChapterImagesDescription": "Lav miniaturebilleder for videoer der har kapitler."
|
||||
}
|
||||
|
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} has finished playing {1} on {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} has been added to your media library",
|
||||
"ValueSpecialEpisodeName": "Special - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Searches the internet for missing subtitles based on metadata configuration.",
|
||||
"TaskDownloadMissingSubtitles": "Download missing subtitles",
|
||||
"TaskRefreshChannelsDescription": "Refreshes internet channel information.",
|
||||
"TaskRefreshChannels": "Refresh Channels",
|
||||
"TaskCleanTranscodeDescription": "Deletes transcode files more than one day old.",
|
||||
"TaskCleanTranscode": "Clean Transcode Directory",
|
||||
"TaskUpdatePluginsDescription": "Downloads and installs updates for plugins that are configured to update automatically.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Updates metadata for actors and directors in your media library.",
|
||||
"TaskRefreshPeople": "Refresh People",
|
||||
"TaskCleanLogsDescription": "Deletes log files that are more than {0} days old.",
|
||||
"TaskCleanLogs": "Clean Log Directory",
|
||||
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
|
||||
"TaskRefreshLibrary": "Scan Media Library",
|
||||
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
|
||||
"TaskRefreshChapterImages": "Extract Chapter Images",
|
||||
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
|
||||
"TaskCleanCache": "Clean Cache Directory",
|
||||
"TasksChannelsCategory": "Internet Channels",
|
||||
"TasksApplicationCategory": "Application",
|
||||
"TasksLibraryCategory": "Library",
|
||||
"TasksMaintenanceCategory": "Maintenance"
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
"Genres": "Géneros",
|
||||
"HeaderAlbumArtists": "Artistas de álbum",
|
||||
"HeaderCameraUploads": "Subidas de cámara",
|
||||
"HeaderContinueWatching": "Continuar viendo",
|
||||
"HeaderContinueWatching": "Seguir viendo",
|
||||
"HeaderFavoriteAlbums": "Álbumes favoritos",
|
||||
"HeaderFavoriteArtists": "Artistas favoritos",
|
||||
"HeaderFavoriteEpisodes": "Episodios favoritos",
|
||||
|
@ -5,7 +5,7 @@
|
||||
"Collections": "Colecciones",
|
||||
"Artists": "Artistas",
|
||||
"DeviceOnlineWithName": "{0} está conectado",
|
||||
"DeviceOfflineWithName": "{0} ha desconectado",
|
||||
"DeviceOfflineWithName": "{0} se ha desconectado",
|
||||
"ChapterNameValue": "Capítulo {0}",
|
||||
"CameraImageUploadedFrom": "Se ha subido una nueva imagen de cámara desde {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado con éxito",
|
||||
|
@ -23,7 +23,7 @@
|
||||
"HeaderFavoriteEpisodes": "قسمتهای مورد علاقه",
|
||||
"HeaderFavoriteShows": "سریالهای مورد علاقه",
|
||||
"HeaderFavoriteSongs": "آهنگهای مورد علاقه",
|
||||
"HeaderLiveTV": "پخش زنده تلویزیون",
|
||||
"HeaderLiveTV": "تلویزیون زنده",
|
||||
"HeaderNextUp": "قسمت بعدی",
|
||||
"HeaderRecordingGroups": "گروههای ضبط",
|
||||
"HomeVideos": "ویدیوهای خانگی",
|
||||
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} پخش {1} را بر روی {2} به پایان رساند",
|
||||
"ValueHasBeenAddedToLibrary": "{0} به کتابخانهی رسانهی شما افزوده شد",
|
||||
"ValueSpecialEpisodeName": "ویژه - {0}",
|
||||
"VersionNumber": "نسخه {0}"
|
||||
"VersionNumber": "نسخه {0}",
|
||||
"TaskCleanTranscodeDescription": "فایلهای کدگذاری که قدیمیتر از یک روز هستند را حذف میکند.",
|
||||
"TaskCleanTranscode": "پاکسازی مسیر کد گذاری",
|
||||
"TaskUpdatePluginsDescription": "دانلود و نصب به روز رسانی افزونههایی که برای به روز رسانی خودکار پیکربندی شدهاند.",
|
||||
"TaskDownloadMissingSubtitlesDescription": "جستجوی زیرنویسهای ناموجود در اینترنت بر اساس پیکربندی ابردادهها.",
|
||||
"TaskDownloadMissingSubtitles": "دانلود زیرنویسهای ناموجود",
|
||||
"TaskRefreshChannelsDescription": "اطلاعات کانال اینترنتی را تازه سازی میکند.",
|
||||
"TaskRefreshChannels": "تازه سازی کانالها",
|
||||
"TaskUpdatePlugins": "به روز رسانی افزونهها",
|
||||
"TaskRefreshPeopleDescription": "ابردادهها برای بازیگران و کارگردانان در کتابخانه رسانه شما به روزرسانی می شوند.",
|
||||
"TaskRefreshPeople": "تازه سازی افراد",
|
||||
"TaskCleanLogsDescription": "واقعه نگارهایی را که قدیمی تر {0} روز هستند را حذف می کند.",
|
||||
"TaskCleanLogs": "پاکسازی مسیر واقعه نگار",
|
||||
"TaskRefreshLibraryDescription": "کتابخانه رسانه شما را اسکن میکند و ابردادهها را تازه سازی میکند.",
|
||||
"TaskRefreshLibrary": "اسکن کتابخانه رسانه",
|
||||
"TaskRefreshChapterImagesDescription": "عکسهای کوچک برای ویدیوهایی که سکانس دارند ایجاد میکند.",
|
||||
"TaskRefreshChapterImages": "استخراج عکسهای سکانس",
|
||||
"TaskCleanCacheDescription": "فایلهای حافظه موقت که توسط سیستم دیگر مورد نیاز نیستند حذف میشوند.",
|
||||
"TaskCleanCache": "پاکسازی مسیر حافظه موقت",
|
||||
"TasksChannelsCategory": "کانالهای داخلی",
|
||||
"TasksApplicationCategory": "برنامه",
|
||||
"TasksLibraryCategory": "کتابخانه",
|
||||
"TasksMaintenanceCategory": "تعمیر"
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"HeaderLiveTV": "TV-lähetykset",
|
||||
"HeaderLiveTV": "Suorat lähetykset",
|
||||
"NewVersionIsAvailable": "Uusi versio Jellyfin palvelimesta on ladattavissa.",
|
||||
"NameSeasonUnknown": "Tuntematon Kausi",
|
||||
"NameSeasonNumber": "Kausi {0}",
|
||||
@ -19,12 +19,12 @@
|
||||
"ItemAddedWithName": "{0} lisättiin kirjastoon",
|
||||
"Inherit": "Periytyä",
|
||||
"HomeVideos": "Kotivideot",
|
||||
"HeaderRecordingGroups": "Nauhoitusryhmät",
|
||||
"HeaderRecordingGroups": "Nauhoiteryhmät",
|
||||
"HeaderNextUp": "Seuraavaksi",
|
||||
"HeaderFavoriteSongs": "Lempikappaleet",
|
||||
"HeaderFavoriteShows": "Lempisarjat",
|
||||
"HeaderFavoriteEpisodes": "Lempijaksot",
|
||||
"HeaderCameraUploads": "Kameralataukset",
|
||||
"HeaderCameraUploads": "Kamerasta Lähetetyt",
|
||||
"HeaderFavoriteArtists": "Lempiartistit",
|
||||
"HeaderFavoriteAlbums": "Lempialbumit",
|
||||
"HeaderContinueWatching": "Jatka katsomista",
|
||||
@ -63,10 +63,10 @@
|
||||
"UserPasswordChangedWithName": "Salasana vaihdettu käyttäjälle {0}",
|
||||
"UserOnlineFromDevice": "{0} on paikalla osoitteesta {1}",
|
||||
"UserOfflineFromDevice": "{0} yhteys katkaistu {1}",
|
||||
"UserLockedOutWithName": "Käyttäjä {0} kirjautui ulos",
|
||||
"UserDownloadingItemWithValues": "{0} latautumassa {1}",
|
||||
"UserDeletedWithName": "Poistettiin käyttäjä {0}",
|
||||
"UserCreatedWithName": "Luotiin käyttäjä {0}",
|
||||
"UserLockedOutWithName": "Käyttäjä {0} lukittu",
|
||||
"UserDownloadingItemWithValues": "{0} lataa {1}",
|
||||
"UserDeletedWithName": "Käyttäjä {0} poistettu",
|
||||
"UserCreatedWithName": "Käyttäjä {0} luotu",
|
||||
"TvShows": "TV-Ohjelmat",
|
||||
"Sync": "Synkronoi",
|
||||
"SubtitleDownloadFailureFromForItem": "Tekstityksen lataaminen epäonnistui {0} - {1}",
|
||||
@ -74,22 +74,44 @@
|
||||
"Songs": "Kappaleet",
|
||||
"Shows": "Ohjelmat",
|
||||
"ServerNameNeedsToBeRestarted": "{0} vaatii uudelleenkäynnistyksen",
|
||||
"ProviderValue": "Palveluntarjoaja: {0}",
|
||||
"ProviderValue": "Tarjoaja: {0}",
|
||||
"Plugin": "Liitännäinen",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toistaminen pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videon toistaminen aloitettu",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä kirjautui ulos",
|
||||
"NotificationOptionTaskFailed": "Ajastetun tehtävän ongelma",
|
||||
"NotificationOptionVideoPlaybackStopped": "Videon toisto pysäytetty",
|
||||
"NotificationOptionVideoPlayback": "Videon toisto aloitettu",
|
||||
"NotificationOptionUserLockedOut": "Käyttäjä lukittu",
|
||||
"NotificationOptionTaskFailed": "Ajastettu tehtävä epäonnistui",
|
||||
"NotificationOptionServerRestartRequired": "Palvelimen uudelleenkäynnistys vaaditaan",
|
||||
"NotificationOptionPluginUpdateInstalled": "Liitännäinen päivitetty",
|
||||
"NotificationOptionPluginUpdateInstalled": "Lisäosan päivitys asennettu",
|
||||
"NotificationOptionPluginUninstalled": "Liitännäinen poistettu",
|
||||
"NotificationOptionPluginInstalled": "Liitännäinen asennettu",
|
||||
"NotificationOptionPluginError": "Ongelma liitännäisessä",
|
||||
"NotificationOptionNewLibraryContent": "Uutta sisältöä lisätty",
|
||||
"NotificationOptionInstallationFailed": "Asennus epäonnistui",
|
||||
"NotificationOptionCameraImageUploaded": "Kuva ladattu kamerasta",
|
||||
"NotificationOptionAudioPlaybackStopped": "Audion toisto pysäytetty",
|
||||
"NotificationOptionAudioPlayback": "Audion toisto aloitettu",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Ohjelmistopäivitys asennettu",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Ohjelmistopäivitys saatavilla"
|
||||
"NotificationOptionCameraImageUploaded": "Kameran kuva ladattu",
|
||||
"NotificationOptionAudioPlaybackStopped": "Äänen toisto lopetettu",
|
||||
"NotificationOptionAudioPlayback": "Toistetaan ääntä",
|
||||
"NotificationOptionApplicationUpdateInstalled": "Uusi sovellusversio asennettu",
|
||||
"NotificationOptionApplicationUpdateAvailable": "Sovelluksesta on uusi versio saatavilla",
|
||||
"TasksMaintenanceCategory": "Ylläpito",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Etsii puuttuvia tekstityksiä videon metadatatietojen pohjalta.",
|
||||
"TaskDownloadMissingSubtitles": "Lataa puuttuvat tekstitykset",
|
||||
"TaskRefreshChannelsDescription": "Päivittää internet-kanavien tiedot.",
|
||||
"TaskRefreshChannels": "Päivitä kanavat",
|
||||
"TaskCleanTranscodeDescription": "Poistaa transkoodatut tiedostot jotka ovat yli päivän vanhoja.",
|
||||
"TaskCleanTranscode": "Puhdista transkoodaushakemisto",
|
||||
"TaskUpdatePluginsDescription": "Lataa ja asentaa päivitykset liitännäisille jotka on asetettu päivittymään automaattisesti.",
|
||||
"TaskUpdatePlugins": "Päivitä liitännäiset",
|
||||
"TaskRefreshPeopleDescription": "Päivittää näyttelijöiden ja ohjaajien mediatiedot kirjastossasi.",
|
||||
"TaskRefreshPeople": "Päivitä henkilöt",
|
||||
"TaskCleanLogsDescription": "Poistaa lokitiedostot jotka ovat yli {0} päivää vanhoja.",
|
||||
"TaskCleanLogs": "Puhdista lokihakemisto",
|
||||
"TaskRefreshLibraryDescription": "Skannaa mediakirjastosi uusien tiedostojen varalle, sekä virkistää metatiedot.",
|
||||
"TaskRefreshLibrary": "Skannaa mediakirjasto",
|
||||
"TaskRefreshChapterImagesDescription": "Luo pienoiskuvat videoille joissa on lukuja.",
|
||||
"TaskRefreshChapterImages": "Eristä lukujen kuvat",
|
||||
"TaskCleanCacheDescription": "Poistaa järjestelmälle tarpeettomat väliaikaistiedostot.",
|
||||
"TaskCleanCache": "Tyhjennä välimuisti-hakemisto",
|
||||
"TasksChannelsCategory": "Internet kanavat",
|
||||
"TasksApplicationCategory": "Sovellus",
|
||||
"TasksLibraryCategory": "Kirjasto"
|
||||
}
|
||||
|
@ -90,5 +90,13 @@
|
||||
"Artists": "Artista",
|
||||
"Application": "Aplikasyon",
|
||||
"AppDeviceValues": "Aplikasyon: {0}, Aparato: {1}",
|
||||
"Albums": "Albums"
|
||||
"Albums": "Albums",
|
||||
"TaskRefreshLibrary": "Suriin ang nasa librerya",
|
||||
"TaskRefreshChapterImagesDescription": "Gumawa ng larawan para sa mga pelikula na may kabanata",
|
||||
"TaskRefreshChapterImages": "Kunin ang mga larawan ng kabanata",
|
||||
"TaskCleanCacheDescription": "Tanggalin ang mga cache file na hindi na kailangan ng systema.",
|
||||
"TasksChannelsCategory": "Palabas sa internet",
|
||||
"TasksLibraryCategory": "Librerya",
|
||||
"TasksMaintenanceCategory": "Pagpapanatili",
|
||||
"HomeVideos": "Sariling pelikula"
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
"Artists": "Artistes",
|
||||
"AuthenticationSucceededWithUserName": "{0} authentifié avec succès",
|
||||
"Books": "Livres",
|
||||
"CameraImageUploadedFrom": "Une nouvelle photo a été chargée depuis {0}",
|
||||
"CameraImageUploadedFrom": "Une nouvelle photographie a été chargée depuis {0}",
|
||||
"Channels": "Chaînes",
|
||||
"ChapterNameValue": "Chapitre {0}",
|
||||
"Collections": "Collections",
|
||||
|
@ -71,7 +71,7 @@
|
||||
"ScheduledTaskFailedWithName": "{0} sikertelen",
|
||||
"ScheduledTaskStartedWithName": "{0} elkezdve",
|
||||
"ServerNameNeedsToBeRestarted": "{0}-t újra kell indítani",
|
||||
"Shows": "Műsorok",
|
||||
"Shows": "Sorozatok",
|
||||
"Songs": "Dalok",
|
||||
"StartupEmbyServerIsLoading": "A Jellyfin Szerver betöltődik. Kérlek, próbáld újra hamarosan.",
|
||||
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
|
||||
|
@ -109,5 +109,8 @@
|
||||
"TaskUpdatePluginsDescription": "自動更新可能なプラグインのアップデートをダウンロードしてインストールします。",
|
||||
"TaskUpdatePlugins": "プラグインの更新",
|
||||
"TaskRefreshPeopleDescription": "メディアライブラリで俳優や監督のメタデータをリフレッシュします。",
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ"
|
||||
"TaskRefreshPeople": "俳優や監督のデータのリフレッシュ",
|
||||
"TaskDownloadMissingSubtitlesDescription": "メタデータ構成に基づいて、欠落している字幕をインターネットで検索します。",
|
||||
"TaskRefreshChapterImagesDescription": "チャプターのあるビデオのサムネイルを作成します。",
|
||||
"TaskRefreshChapterImages": "チャプター画像を抽出する"
|
||||
}
|
||||
|
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
61
Emby.Server.Implementations/Localization/Core/mr.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"Books": "पुस्तकं",
|
||||
"Artists": "संगीतकार",
|
||||
"Albums": "अल्बम",
|
||||
"Playlists": "प्लेलिस्ट",
|
||||
"HeaderAlbumArtists": "अल्बम संगीतकार",
|
||||
"Folders": "फोल्डर",
|
||||
"HeaderFavoriteEpisodes": "आवडते भाग",
|
||||
"HeaderFavoriteSongs": "आवडती गाणी",
|
||||
"Movies": "चित्रपट",
|
||||
"HeaderFavoriteArtists": "आवडते संगीतकार",
|
||||
"Shows": "कार्यक्रम",
|
||||
"HeaderFavoriteAlbums": "आवडते अल्बम",
|
||||
"Channels": "वाहिन्या",
|
||||
"ValueSpecialEpisodeName": "विशेष - {0}",
|
||||
"HeaderFavoriteShows": "आवडते कार्यक्रम",
|
||||
"Favorites": "आवडीचे",
|
||||
"HeaderNextUp": "यानंतर",
|
||||
"Songs": "गाणी",
|
||||
"HeaderLiveTV": "लाइव्ह टीव्ही",
|
||||
"Genres": "जाँनरे",
|
||||
"Photos": "चित्र",
|
||||
"TaskDownloadMissingSubtitles": "नसलेले सबटायटल डाउनलोड करा",
|
||||
"TaskCleanTranscodeDescription": "एक दिवसापेक्षा जुन्या ट्रान्सकोड फायली काढून टाका.",
|
||||
"TaskCleanTranscode": "ट्रान्सकोड डिरेक्टरी साफ करून टाका",
|
||||
"TaskUpdatePlugins": "प्लगइन अपडेट करा",
|
||||
"TaskCleanLogs": "लॉग डिरेक्टरी साफ करून टाका",
|
||||
"TaskCleanCache": "कॅश डिरेक्टरी साफ करून टाका",
|
||||
"TasksChannelsCategory": "इंटरनेट वाहिन्या",
|
||||
"TasksApplicationCategory": "अॅप्लिकेशन",
|
||||
"TasksLibraryCategory": "संग्रहालय",
|
||||
"VersionNumber": "आवृत्ती {0}",
|
||||
"UserPasswordChangedWithName": "{0} या प्रयोक्त्याचे पासवर्ड बदलण्यात आले आहे",
|
||||
"UserOnlineFromDevice": "{0} हे {1} येथून ऑनलाइन आहेत",
|
||||
"UserDeletedWithName": "प्रयोक्ता {0} काढून टाकण्यात आले आहे",
|
||||
"UserCreatedWithName": "प्रयोक्ता {0} बनवण्यात आले आहे",
|
||||
"User": "प्रयोक्ता",
|
||||
"TvShows": "टीव्ही कार्यक्रम",
|
||||
"StartupEmbyServerIsLoading": "जेलिफिन सर्व्हर लोड होत आहे. कृपया थोड्या वेळात पुन्हा प्रयत्न करा.",
|
||||
"Plugin": "प्लगइन",
|
||||
"NotificationOptionCameraImageUploaded": "कॅमेरा चित्र अपलोड केले आहे",
|
||||
"NotificationOptionApplicationUpdateInstalled": "अॅप्लिकेशन अपडेट इन्स्टॉल केले आहे",
|
||||
"NotificationOptionApplicationUpdateAvailable": "अॅप्लिकेशन अपडेट उपलब्ध आहे",
|
||||
"NewVersionIsAvailable": "जेलिफिन सर्व्हरची एक नवीन आवृत्ती डाउनलोड करण्यास उपलब्ध आहे.",
|
||||
"NameSeasonUnknown": "अज्ञात सीझन",
|
||||
"NameSeasonNumber": "सीझन {0}",
|
||||
"MusicVideos": "संगीत व्हिडीयो",
|
||||
"Music": "संगीत",
|
||||
"MessageApplicationUpdatedTo": "जेलिफिन सर्व्हर अपडेट होऊन {0} आवृत्तीवर पोहोचला आहे",
|
||||
"MessageApplicationUpdated": "जेलिफिन सर्व्हर अपडेट केला गेला आहे",
|
||||
"Latest": "नवीनतम",
|
||||
"LabelIpAddressValue": "आयपी पत्ता: {0}",
|
||||
"ItemRemovedWithName": "{0} हे संग्रहालयातून काढून टाकण्यात आले",
|
||||
"ItemAddedWithName": "{0} हे संग्रहालयात जोडले गेले",
|
||||
"HomeVideos": "घरचे व्हिडीयो",
|
||||
"HeaderRecordingGroups": "रेकॉर्डिंग गट",
|
||||
"HeaderCameraUploads": "कॅमेरा अपलोड",
|
||||
"CameraImageUploadedFrom": "एक नवीन कॅमेरा चित्र {0} येथून अपलोड केले आहे",
|
||||
"Application": "अॅप्लिकेशन",
|
||||
"AppDeviceValues": "अॅप: {0}, यंत्र: {1}"
|
||||
}
|
@ -92,5 +92,27 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} heeft afspelen van {1} gestopt op {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} is toegevoegd aan je mediabibliotheek",
|
||||
"ValueSpecialEpisodeName": "Speciaal - {0}",
|
||||
"VersionNumber": "Versie {0}"
|
||||
"VersionNumber": "Versie {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Zoekt op het internet naar missende ondertitels gebaseerd op metadata configuratie.",
|
||||
"TaskDownloadMissingSubtitles": "Download missende ondertitels",
|
||||
"TaskRefreshChannelsDescription": "Vernieuwt informatie van internet kanalen.",
|
||||
"TaskRefreshChannels": "Vernieuw Kanalen",
|
||||
"TaskCleanTranscodeDescription": "Verwijder transcode bestanden ouder dan 1 dag.",
|
||||
"TaskCleanLogs": "Log Folder Opschonen",
|
||||
"TaskCleanTranscode": "Transcode Folder Opschonen",
|
||||
"TaskUpdatePluginsDescription": "Download en installeert updates voor plugins waar automatisch updaten aan staat.",
|
||||
"TaskUpdatePlugins": "Update Plugins",
|
||||
"TaskRefreshPeopleDescription": "Update metadata for acteurs en regisseurs in de media bibliotheek.",
|
||||
"TaskRefreshPeople": "Vernieuw Personen",
|
||||
"TaskCleanLogsDescription": "Verwijdert log bestanden ouder dan {0} dagen.",
|
||||
"TaskRefreshLibraryDescription": "Scant de media bibliotheek voor nieuwe bestanden en vernieuwt de metadata.",
|
||||
"TaskRefreshLibrary": "Scan Media Bibliotheek",
|
||||
"TaskRefreshChapterImagesDescription": "Maakt thumbnails aan voor videos met hoofdstukken.",
|
||||
"TaskRefreshChapterImages": "Hoofdstukafbeeldingen Uitpakken",
|
||||
"TaskCleanCacheDescription": "Verwijder gecachte bestanden die het systeem niet langer nodig heeft.",
|
||||
"TaskCleanCache": "Cache Folder Opschonen",
|
||||
"TasksChannelsCategory": "Internet Kanalen",
|
||||
"TasksApplicationCategory": "Applicatie",
|
||||
"TasksLibraryCategory": "Bibliotheek",
|
||||
"TasksMaintenanceCategory": "Onderhoud"
|
||||
}
|
||||
|
@ -91,5 +91,9 @@
|
||||
"CameraImageUploadedFrom": "Uma nova imagem da câmara foi enviada a partir de {0}",
|
||||
"AuthenticationSucceededWithUserName": "{0} autenticado com sucesso",
|
||||
"Application": "Aplicação",
|
||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}"
|
||||
"AppDeviceValues": "Aplicação {0}, Dispositivo: {1}",
|
||||
"TaskCleanCache": "Limpar Diretório de Cache",
|
||||
"TasksApplicationCategory": "Aplicação",
|
||||
"TasksLibraryCategory": "Biblioteca",
|
||||
"TasksMaintenanceCategory": "Manutenção"
|
||||
}
|
||||
|
@ -92,5 +92,26 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0} har avslutat uppspelningen av {1} på {2}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} har lagts till i ditt mediebibliotek",
|
||||
"ValueSpecialEpisodeName": "Specialavsnitt - {0}",
|
||||
"VersionNumber": "Version {0}"
|
||||
"VersionNumber": "Version {0}",
|
||||
"TaskDownloadMissingSubtitlesDescription": "Söker på internet efter saknade undertexter baserad på metadatas konfiguration.",
|
||||
"TaskDownloadMissingSubtitles": "Ladda ned saknade undertexter",
|
||||
"TaskRefreshChannelsDescription": "Uppdaterar information för internetkanaler.",
|
||||
"TaskRefreshChannels": "Uppdatera kanaler",
|
||||
"TaskCleanTranscodeDescription": "Raderar transkodningsfiler som är mer än en dag gamla.",
|
||||
"TaskCleanTranscode": "Töm transkodningskatalog",
|
||||
"TaskUpdatePluginsDescription": "Laddar ned och installerar uppdateringar till insticksprogram som är konfigurerade att uppdateras automatiskt.",
|
||||
"TaskUpdatePlugins": "Uppdatera insticksprogram",
|
||||
"TaskRefreshPeopleDescription": "Uppdaterar metadata för skådespelare och regissörer i ditt mediabibliotek.",
|
||||
"TaskCleanLogsDescription": "Raderar loggfiler som är mer än {0} dagar gamla.",
|
||||
"TaskCleanLogs": "Töm loggkatalog",
|
||||
"TaskRefreshLibraryDescription": "Söker igenom ditt mediabibliotek efter nya filer och förnyar metadata.",
|
||||
"TaskRefreshLibrary": "Genomsök mediabibliotek",
|
||||
"TaskRefreshChapterImagesDescription": "Skapa miniatyrbilder för videor med kapitel.",
|
||||
"TaskRefreshChapterImages": "Extrahera kapitelbilder",
|
||||
"TaskCleanCacheDescription": "Radera cachade filer som systemet inte längre behöver.",
|
||||
"TaskCleanCache": "Rensa cachekatalog",
|
||||
"TasksChannelsCategory": "Internetkanaler",
|
||||
"TasksApplicationCategory": "Applikation",
|
||||
"TasksLibraryCategory": "Bibliotek",
|
||||
"TasksMaintenanceCategory": "Underhåll"
|
||||
}
|
||||
|
@ -92,5 +92,10 @@
|
||||
"UserStoppedPlayingItemWithValues": "{0}, {2} cihazında {1} izlemeyi bitirdi",
|
||||
"ValueHasBeenAddedToLibrary": "Medya kitaplığınıza {0} eklendi",
|
||||
"ValueSpecialEpisodeName": "Özel - {0}",
|
||||
"VersionNumber": "Versiyon {0}"
|
||||
"VersionNumber": "Versiyon {0}",
|
||||
"TaskCleanCache": "Geçici dosya klasörünü temizle",
|
||||
"TasksChannelsCategory": "İnternet kanalları",
|
||||
"TasksApplicationCategory": "Yazılım",
|
||||
"TasksLibraryCategory": "Kütüphane",
|
||||
"TasksMaintenanceCategory": "Onarım"
|
||||
}
|
||||
|
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
117
Emby.Server.Implementations/Localization/Core/ur_PK.json
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"HeaderFavoriteAlbums": "پسندیدہ البمز",
|
||||
"HeaderNextUp": "اگلا",
|
||||
"HeaderFavoriteArtists": "پسندیدہ فنکار",
|
||||
"HeaderAlbumArtists": "البم کے فنکار",
|
||||
"Movies": "فلمیں",
|
||||
"HeaderFavoriteEpisodes": "پسندیدہ اقساط",
|
||||
"Collections": "مجموعہ",
|
||||
"Folders": "فولڈرز",
|
||||
"HeaderLiveTV": "براہ راست ٹی وی",
|
||||
"Channels": "چینل",
|
||||
"HeaderContinueWatching": "دیکھنا جاری رکھیں",
|
||||
"Playlists": "پلے لسٹس",
|
||||
"ValueSpecialEpisodeName": "خاص - {0}",
|
||||
"Shows": "شوز",
|
||||
"Genres": "انواع",
|
||||
"Artists": "فنکار",
|
||||
"Sync": "مطابقت",
|
||||
"Photos": "تصوریں",
|
||||
"Albums": "البم",
|
||||
"Favorites": "پسندیدہ",
|
||||
"Songs": "گانے",
|
||||
"Books": "کتابیں",
|
||||
"HeaderFavoriteSongs": "پسندیدہ گانے",
|
||||
"HeaderFavoriteShows": "پسندیدہ شوز",
|
||||
"TaskDownloadMissingSubtitlesDescription": "میٹا ڈیٹا کی تشکیل پر مبنی ذیلی عنوانات کے غائب عنوانات انٹرنیٹ پے تلاش کرتا ہے۔",
|
||||
"TaskDownloadMissingSubtitles": "غائب سب ٹائٹلز ڈاؤن لوڈ کریں",
|
||||
"TaskRefreshChannelsDescription": "انٹرنیٹ چینل کی معلومات کو تازہ دم کرتا ہے۔",
|
||||
"TaskRefreshChannels": "چینلز ریفریش کریں",
|
||||
"TaskCleanTranscodeDescription": "ایک دن سے زیادہ پرانی ٹرانسکوڈ فائلوں کو حذف کرتا ہے۔",
|
||||
"TaskCleanTranscode": "ٹرانس کوڈ ڈائرکٹری صاف کریں",
|
||||
"TaskUpdatePluginsDescription": "پلگ انز کے لئے اپ ڈیٹس ڈاؤن لوڈ اور انسٹال کرتے ہیں جو خود بخود اپ ڈیٹ کرنے کیلئے تشکیل شدہ ہیں۔",
|
||||
"TaskUpdatePlugins": "پلگ انز کو اپ ڈیٹ کریں",
|
||||
"TaskRefreshPeopleDescription": "آپ کی میڈیا لائبریری میں اداکاروں اور ہدایت کاروں کے لئے میٹا ڈیٹا کی تازہ کاری۔",
|
||||
"TaskRefreshPeople": "لوگوں کو تروتازہ کریں",
|
||||
"TaskCleanLogsDescription": "لاگ فائلوں کو حذف کریں جو {0} دن سے زیادہ پرانی ہیں۔",
|
||||
"TaskCleanLogs": "لاگ ڈائرکٹری کو صاف کریں",
|
||||
"TaskRefreshLibraryDescription": "میڈیا لائبریری کو اسکین کرتا ھے ہر میٹا دیٹا کہ تازہ دم کرتا ھے.",
|
||||
"TaskRefreshLibrary": "اسکین میڈیا لائبریری",
|
||||
"TaskRefreshChapterImagesDescription": "بابوں والی ویڈیوز کے لئے تمبنیل بنایں۔",
|
||||
"TaskRefreshChapterImages": "باب کی تصاویر نکالیں",
|
||||
"TaskCleanCacheDescription": "فائلوں کو حذف کریں جنکی ضرورت نھیں ھے۔",
|
||||
"TaskCleanCache": "کیش ڈائرکٹری کلیر کریں",
|
||||
"TasksChannelsCategory": "انٹرنیٹ چینلز",
|
||||
"TasksApplicationCategory": "پروگرام",
|
||||
"TasksLibraryCategory": "لآیبریری",
|
||||
"TasksMaintenanceCategory": "مرمت",
|
||||
"VersionNumber": "ورژن {0}",
|
||||
"ValueHasBeenAddedToLibrary": "{0} آپ کی میڈیا لائبریری میں شامل کر دیا گیا ہے",
|
||||
"UserStoppedPlayingItemWithValues": "{0} نے {1} چلانا ختم کر دیا ھے {2} پے",
|
||||
"UserStartedPlayingItemWithValues": "{0} چلا رہا ہے {1} {2} پے",
|
||||
"UserPolicyUpdatedWithName": "صارف {0} کی پالیسی کیلئے تازہ کاری کی گئی ہے",
|
||||
"UserPasswordChangedWithName": "صارف {0} کے لئے پاس ورڈ تبدیل کر دیا گیا ہے",
|
||||
"UserOnlineFromDevice": "{0} آن لائن ہے {1} سے",
|
||||
"UserOfflineFromDevice": "{0} سے منقطع ہوگیا ہے {1}",
|
||||
"UserLockedOutWithName": "صارف {0} کو لاک آؤٹ کردیا گیا ہے",
|
||||
"UserDownloadingItemWithValues": "{0} ڈاؤن لوڈ کر رھا ھے {1}",
|
||||
"UserDeletedWithName": "صارف {0} کو ہٹا دیا گیا ہے",
|
||||
"UserCreatedWithName": "صارف {0} تشکیل دیا گیا ہے",
|
||||
"User": "صارف",
|
||||
"TvShows": "ٹی وی کے پروگرام",
|
||||
"System": "نظام",
|
||||
"SubtitleDownloadFailureFromForItem": "ذیلی عنوانات {0} سے ڈاؤن لوڈ کرنے میں ناکام {1} کے لیے",
|
||||
"StartupEmbyServerIsLoading": "جیلیفن سرور لوڈ ہورہا ہے۔ براہ کرم جلد ہی دوبارہ کوشش کریں۔",
|
||||
"ServerNameNeedsToBeRestarted": "{0} دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||
"ScheduledTaskStartedWithName": "{0} شروع",
|
||||
"ScheduledTaskFailedWithName": "{0} ناکام",
|
||||
"ProviderValue": "فراہم کرنے والا: {0}",
|
||||
"PluginUpdatedWithName": "{0} تازہ کاری کی گئی تھی",
|
||||
"PluginUninstalledWithName": "[0} ہٹا دیا گیا تھا",
|
||||
"PluginInstalledWithName": "{0} انسٹال کیا گیا تھا",
|
||||
"Plugin": "پلگن",
|
||||
"NotificationOptionVideoPlaybackStopped": "ویڈیو پلے بیک رک گیا",
|
||||
"NotificationOptionVideoPlayback": "ویڈیو پلے بیک شروع ہوا",
|
||||
"NotificationOptionUserLockedOut": "صارف کو لاک آؤٹ کیا گیا",
|
||||
"NotificationOptionTaskFailed": "طے شدہ کام کی ناکامی",
|
||||
"NotificationOptionServerRestartRequired": "سرور دوبارہ چلانے کرنے کی ضرورت ہے",
|
||||
"NotificationOptionPluginUpdateInstalled": "پلگ ان اپ ڈیٹ انسٹال",
|
||||
"NotificationOptionPluginUninstalled": "پلگ ان ہٹا دیا گیا",
|
||||
"NotificationOptionPluginInstalled": "پلگ ان انسٹال ہوا",
|
||||
"NotificationOptionPluginError": "پلگ ان کی ناکامی",
|
||||
"NotificationOptionNewLibraryContent": "نیا مواد شامل کیا گیا",
|
||||
"NotificationOptionInstallationFailed": "تنصیب کی ناکامی",
|
||||
"NotificationOptionCameraImageUploaded": "کیمرے کی تصویر اپ لوڈ ہوگئی",
|
||||
"NotificationOptionAudioPlaybackStopped": "آڈیو پلے بیک رک گیا",
|
||||
"NotificationOptionAudioPlayback": "آڈیو پلے بیک شروع ہوا",
|
||||
"NotificationOptionApplicationUpdateInstalled": "پروگرام اپ ڈیٹ انسٹال ہوچکا ھے",
|
||||
"NotificationOptionApplicationUpdateAvailable": "پروگرام کی تازہ کاری دستیاب ہے",
|
||||
"NewVersionIsAvailable": "جیلیفن سرور کا ایک نیا ورژن ڈاؤن لوڈ کے لئے دستیاب ہے۔",
|
||||
"NameSeasonUnknown": "نامعلوم باب",
|
||||
"NameSeasonNumber": "باب {0}",
|
||||
"NameInstallFailed": "{0} تنصیب ناکام ہوگئی",
|
||||
"MusicVideos": "موسیقی ویڈیو",
|
||||
"Music": "موسیقی",
|
||||
"MixedContent": "مخلوط مواد",
|
||||
"MessageServerConfigurationUpdated": "سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||
"MessageNamedServerConfigurationUpdatedWithValue": "سرور ضمن {0} کو ترتیب دے دیا گیا ھے",
|
||||
"MessageApplicationUpdatedTo": "جیلیفن سرور کو اپ ڈیٹ کیا ہے {0}",
|
||||
"MessageApplicationUpdated": "جیلیفن سرور کو اپ ڈیٹ کر دیا گیا ہے",
|
||||
"Latest": "تازہ ترین",
|
||||
"LabelRunningTimeValue": "چلانے کی مدت",
|
||||
"LabelIpAddressValue": "ای پی پتے {0}",
|
||||
"ItemRemovedWithName": "لائبریری سے ہٹا دیا گیا ھے",
|
||||
"ItemAddedWithName": "[0} لائبریری میں شامل کیا گیا ھے",
|
||||
"Inherit": "وراثت میں",
|
||||
"HomeVideos": "ہوم ویڈیو",
|
||||
"HeaderRecordingGroups": "ریکارڈنگ گروپس",
|
||||
"HeaderCameraUploads": "کیمرہ اپلوڈز",
|
||||
"FailedLoginAttemptWithUserName": "لاگن کئ کوشش ناکام {0}",
|
||||
"DeviceOnlineWithName": "{0} متصل ھو چکا ھے",
|
||||
"DeviceOfflineWithName": "{0} منقطع ھو چکا ھے",
|
||||
"ChapterNameValue": "باب",
|
||||
"AuthenticationSucceededWithUserName": "{0} کامیابی کے ساتھ تصدیق ھوچکی ھے",
|
||||
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
|
||||
"Application": "پروگرام",
|
||||
"AppDeviceValues": "پروگرام:{0}, آلہ:{1}"
|
||||
}
|
@ -55,9 +55,8 @@ namespace Emby.Server.Implementations.ScheduledTasks
|
||||
{
|
||||
progress.Report(0);
|
||||
|
||||
var packagesToInstall = await _installationManager.GetAvailablePluginUpdates(cancellationToken)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var packageFetchTask = _installationManager.GetAvailablePluginUpdates(cancellationToken);
|
||||
var packagesToInstall = (await packageFetchTask.ConfigureAwait(false)).ToList();
|
||||
|
||||
progress.Report(10);
|
||||
|
||||
|
@ -3,8 +3,10 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -18,6 +20,7 @@ using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
@ -27,6 +30,11 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// </summary>
|
||||
public class InstallationManager : IInstallationManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The key for a setting that specifies a URL for the plugin repository JSON manifest.
|
||||
/// </summary>
|
||||
public const string PluginManifestUrlKey = "InstallationManager:PluginManifestUrl";
|
||||
|
||||
/// <summary>
|
||||
/// The _logger.
|
||||
/// </summary>
|
||||
@ -44,6 +52,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly IZipClient _zipClient;
|
||||
private readonly IConfiguration _appConfig;
|
||||
|
||||
private readonly object _currentInstallationsLock = new object();
|
||||
|
||||
@ -65,7 +74,8 @@ namespace Emby.Server.Implementations.Updates
|
||||
IJsonSerializer jsonSerializer,
|
||||
IServerConfigurationManager config,
|
||||
IFileSystem fileSystem,
|
||||
IZipClient zipClient)
|
||||
IZipClient zipClient,
|
||||
IConfiguration appConfig)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
@ -83,6 +93,7 @@ namespace Emby.Server.Implementations.Updates
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_zipClient = zipClient;
|
||||
_appConfig = appConfig;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@ -112,19 +123,43 @@ namespace Emby.Server.Implementations.Updates
|
||||
/// <inheritdoc />
|
||||
public async Task<IReadOnlyList<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = "https://repo.jellyfin.org/releases/plugin/manifest.json",
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
var manifestUrl = _appConfig.GetValue<string>(PluginManifestUrlKey);
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(
|
||||
stream).ConfigureAwait(false);
|
||||
using (var response = await _httpClient.SendAsync(
|
||||
new HttpRequestOptions
|
||||
{
|
||||
Url = manifestUrl,
|
||||
CancellationToken = cancellationToken,
|
||||
CacheMode = CacheMode.Unconditional,
|
||||
CacheLength = TimeSpan.FromMinutes(3)
|
||||
},
|
||||
HttpMethod.Get).ConfigureAwait(false))
|
||||
using (Stream stream = response.Content)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _jsonSerializer.DeserializeFromStreamAsync<IReadOnlyList<PackageInfo>>(stream).ConfigureAwait(false);
|
||||
}
|
||||
catch (SerializationException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"Failed to deserialize the plugin manifest retrieved from {PluginManifestUrl}. If you " +
|
||||
"have specified a custom plugin repository manifest URL with --plugin-manifest-url or " +
|
||||
PluginManifestUrlKey + ", please ensure that it is correct.";
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (UriFormatException ex)
|
||||
{
|
||||
const string LogTemplate =
|
||||
"The URL configured for the plugin repository manifest URL is not valid: {PluginManifestUrl}. " +
|
||||
"Please check the URL configured by --plugin-manifest-url or " + PluginManifestUrlKey;
|
||||
_logger.LogError(ex, LogTemplate, manifestUrl);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,16 +224,17 @@ namespace Emby.Server.Implementations.Updates
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var catalog = await GetAvailablePackages(cancellationToken).ConfigureAwait(false);
|
||||
return GetAvailablePluginUpdates(catalog);
|
||||
}
|
||||
|
||||
var systemUpdateLevel = _applicationHost.SystemUpdateLevel;
|
||||
|
||||
// Figure out what needs to be installed
|
||||
private IEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(IReadOnlyList<PackageInfo> pluginCatalog)
|
||||
{
|
||||
foreach (var plugin in _applicationHost.Plugins)
|
||||
{
|
||||
var compatibleversions = GetCompatibleVersions(catalog, plugin.Name, plugin.Id, plugin.Version, systemUpdateLevel);
|
||||
var compatibleversions = GetCompatibleVersions(pluginCatalog, plugin.Name, plugin.Id, plugin.Version, _applicationHost.SystemUpdateLevel);
|
||||
var version = compatibleversions.FirstOrDefault(y => y.Version > plugin.Version);
|
||||
if (version != null
|
||||
&& !CompletedInstallations.Any(x => string.Equals(x.AssemblyGuid, version.guid, StringComparison.OrdinalIgnoreCase)))
|
||||
|
@ -8,10 +8,10 @@
|
||||
},
|
||||
"Jellyfin.Server (nowebclient)": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "--nowebclient",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"commandLineArgs": "--nowebclient"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using CommandLine;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
|
||||
namespace Jellyfin.Server
|
||||
@ -76,6 +76,10 @@ namespace Jellyfin.Server
|
||||
[Option("restartargs", Required = false, HelpText = "Arguments for restart script.")]
|
||||
public string? RestartArgs { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
[Option("plugin-manifest-url", Required = false, HelpText = "A custom URL for the plugin repository JSON manifest")]
|
||||
public string? PluginManifestUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command line options as a dictionary that can be used in the .NET configuration system.
|
||||
/// </summary>
|
||||
@ -84,6 +88,11 @@ namespace Jellyfin.Server
|
||||
{
|
||||
var config = new Dictionary<string, string>();
|
||||
|
||||
if (PluginManifestUrl != null)
|
||||
{
|
||||
config.Add(InstallationManager.PluginManifestUrlKey, PluginManifestUrl);
|
||||
}
|
||||
|
||||
if (NoWebClient)
|
||||
{
|
||||
config.Add(ConfigurationExtensions.HostWebClientKey, bool.FalseString);
|
||||
|
@ -86,12 +86,9 @@ namespace MediaBrowser.Api
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
if (removeEmpty)
|
||||
{
|
||||
return value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
return value.Split(separator);
|
||||
return removeEmpty
|
||||
? value.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
|
||||
: value.Split(separator);
|
||||
}
|
||||
|
||||
public SemaphoreSlim GetTranscodingLock(string outputPath)
|
||||
@ -258,7 +255,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
|
||||
{
|
||||
var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null;
|
||||
var ticks = transcodingPosition?.Ticks;
|
||||
|
||||
if (job != null)
|
||||
{
|
||||
@ -487,16 +484,9 @@ namespace MediaBrowser.Api
|
||||
/// <returns>Task.</returns>
|
||||
internal Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
|
||||
{
|
||||
return KillTranscodingJobs(j =>
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(playSessionId))
|
||||
{
|
||||
return string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
}, deleteFiles);
|
||||
return KillTranscodingJobs(j => string.IsNullOrWhiteSpace(playSessionId)
|
||||
? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
|
||||
: string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -561,10 +551,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
lock (job.ProcessLock)
|
||||
{
|
||||
if (job.TranscodingThrottler != null)
|
||||
{
|
||||
job.TranscodingThrottler.Stop().GetAwaiter().GetResult();
|
||||
}
|
||||
job.TranscodingThrottler?.Stop().GetAwaiter().GetResult();
|
||||
|
||||
var process = job.Process;
|
||||
|
||||
|
@ -58,12 +58,9 @@ namespace MediaBrowser.Api
|
||||
|
||||
public static string[] SplitValue(string value, char delim)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return value == null
|
||||
? Array.Empty<string>()
|
||||
: value.Split(new[] { delim }, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
|
||||
public static Guid[] GetGuids(string value)
|
||||
@ -97,19 +94,10 @@ namespace MediaBrowser.Api
|
||||
var authenticatedUser = auth.User;
|
||||
|
||||
// If they're going to update the record of another user, they must be an administrator
|
||||
if (!userId.Equals(auth.UserId))
|
||||
if ((!userId.Equals(auth.UserId) && !authenticatedUser.Policy.IsAdministrator)
|
||||
|| (restrictUserPreferences && !authenticatedUser.Policy.EnableUserPreferenceAccess))
|
||||
{
|
||||
if (!authenticatedUser.Policy.IsAdministrator)
|
||||
{
|
||||
throw new SecurityException("Unauthorized access.");
|
||||
}
|
||||
}
|
||||
else if (restrictUserPreferences)
|
||||
{
|
||||
if (!authenticatedUser.Policy.EnableUserPreferenceAccess)
|
||||
{
|
||||
throw new SecurityException("Unauthorized access.");
|
||||
}
|
||||
throw new SecurityException("Unauthorized access.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,8 +126,8 @@ namespace MediaBrowser.Api
|
||||
options.Fields = hasFields.GetItemFields();
|
||||
}
|
||||
|
||||
if (!options.ContainsField(Model.Querying.ItemFields.RecursiveItemCount)
|
||||
|| !options.ContainsField(Model.Querying.ItemFields.ChildCount))
|
||||
if (!options.ContainsField(ItemFields.RecursiveItemCount)
|
||||
|| !options.ContainsField(ItemFields.ChildCount))
|
||||
{
|
||||
var client = authContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
|
||||
if (client.IndexOf("kodi", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||
@ -150,7 +138,7 @@ namespace MediaBrowser.Api
|
||||
int oldLen = options.Fields.Length;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
options.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = Model.Querying.ItemFields.RecursiveItemCount;
|
||||
arr[oldLen] = ItemFields.RecursiveItemCount;
|
||||
options.Fields = arr;
|
||||
}
|
||||
|
||||
@ -166,7 +154,7 @@ namespace MediaBrowser.Api
|
||||
int oldLen = options.Fields.Length;
|
||||
var arr = new ItemFields[oldLen + 1];
|
||||
options.Fields.CopyTo(arr, 0);
|
||||
arr[oldLen] = Model.Querying.ItemFields.ChildCount;
|
||||
arr[oldLen] = ItemFields.ChildCount;
|
||||
options.Fields = arr;
|
||||
}
|
||||
}
|
||||
@ -282,27 +270,21 @@ namespace MediaBrowser.Api
|
||||
|
||||
}).OfType<T>().FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
result ??= libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
result = libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Name = name.Replace(BaseItem.SlugChar, '/'),
|
||||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
DtoOptions = dtoOptions
|
||||
Name = name.Replace(BaseItem.SlugChar, '/'),
|
||||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
DtoOptions = dtoOptions
|
||||
|
||||
}).OfType<T>().FirstOrDefault();
|
||||
}
|
||||
}).OfType<T>().FirstOrDefault();
|
||||
|
||||
if (result == null)
|
||||
result ??= libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
result = libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
Name = name.Replace(BaseItem.SlugChar, '?'),
|
||||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
DtoOptions = dtoOptions
|
||||
Name = name.Replace(BaseItem.SlugChar, '?'),
|
||||
IncludeItemTypes = new[] { typeof(T).Name },
|
||||
DtoOptions = dtoOptions
|
||||
|
||||
}).OfType<T>().FirstOrDefault();
|
||||
}
|
||||
}).OfType<T>().FirstOrDefault();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -116,12 +116,9 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
var val = Filters;
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return new ItemFilter[] { };
|
||||
}
|
||||
|
||||
return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
|
||||
return string.IsNullOrEmpty(val)
|
||||
? Array.Empty<ItemFilter>()
|
||||
: val.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -173,14 +170,9 @@ namespace MediaBrowser.Api
|
||||
/// <returns>IEnumerable{ItemFilter}.</returns>
|
||||
public IEnumerable<ItemFilter> GetFilters()
|
||||
{
|
||||
var val = Filters;
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return new ItemFilter[] { };
|
||||
}
|
||||
|
||||
return val.Split(',').Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true));
|
||||
return string.IsNullOrEmpty(Filters)
|
||||
? Array.Empty<ItemFilter>()
|
||||
: Filters.Split(',').Select(v => Enum.Parse<ItemFilter>(v, true));
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,7 +233,7 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
Limit = request.Limit,
|
||||
StartIndex = request.StartIndex,
|
||||
ChannelIds = new Guid[] { new Guid(request.Id) },
|
||||
ChannelIds = new[] { new Guid(request.Id) },
|
||||
ParentId = string.IsNullOrWhiteSpace(request.FolderId) ? Guid.Empty : new Guid(request.FolderId),
|
||||
OrderBy = request.GetOrderBy(),
|
||||
DtoOptions = new Controller.Dto.DtoOptions
|
||||
|
@ -155,16 +155,14 @@ namespace MediaBrowser.Api.Devices
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
||||
{
|
||||
return _deviceManager.AcceptCameraUpload(deviceId, request.RequestStream, new LocalFileInfo
|
||||
{
|
||||
MimeType = Request.ContentType,
|
||||
Album = album,
|
||||
Name = name,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
MimeType = Request.ContentType,
|
||||
Album = album,
|
||||
Name = name,
|
||||
Id = id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -253,12 +253,7 @@ namespace MediaBrowser.Api
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!request.IncludeDirectories && isDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return request.IncludeDirectories || !isDirectory;
|
||||
});
|
||||
|
||||
return entries.Select(f => new FileSystemEntryInfo(f.Name, f.FullName, f.IsDirectory ? FileSystemEntryType.Directory : FileSystemEntryType.File));
|
||||
|
@ -133,7 +133,7 @@ namespace MediaBrowser.Api
|
||||
// Non recursive not yet supported for library folders
|
||||
if ((request.Recursive ?? true) || parentItem is UserView || parentItem is ICollectionFolder)
|
||||
{
|
||||
genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new Guid[] { parentItem.Id };
|
||||
genreQuery.AncestorIds = parentItem == null ? Array.Empty<Guid>() : new[] { parentItem.Id };
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -231,7 +231,7 @@ namespace MediaBrowser.Api
|
||||
EnableTotalRecordCount = false,
|
||||
DtoOptions = new Controller.Dto.DtoOptions
|
||||
{
|
||||
Fields = new ItemFields[] { ItemFields.Genres, ItemFields.Tags },
|
||||
Fields = new[] { ItemFields.Genres, ItemFields.Tags },
|
||||
EnableImages = false,
|
||||
EnableUserData = false
|
||||
}
|
||||
|
@ -650,7 +650,7 @@ namespace MediaBrowser.Api.Images
|
||||
if (!string.IsNullOrWhiteSpace(request.Format)
|
||||
&& Enum.TryParse(request.Format, true, out ImageFormat format))
|
||||
{
|
||||
return new ImageFormat[] { format };
|
||||
return new[] { format };
|
||||
}
|
||||
|
||||
return GetClientSupportedFormats();
|
||||
@ -743,24 +743,22 @@ namespace MediaBrowser.Api.Images
|
||||
/// <returns>Task.</returns>
|
||||
public async Task PostImage(BaseItem entity, Stream inputStream, ImageType imageType, string mimeType)
|
||||
{
|
||||
using (var reader = new StreamReader(inputStream))
|
||||
using var reader = new StreamReader(inputStream);
|
||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
|
||||
var bytes = Convert.FromBase64String(text);
|
||||
|
||||
var memoryStream = new MemoryStream(bytes)
|
||||
{
|
||||
var text = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
Position = 0
|
||||
};
|
||||
|
||||
var bytes = Convert.FromBase64String(text);
|
||||
// Handle image/png; charset=utf-8
|
||||
mimeType = mimeType.Split(';').FirstOrDefault();
|
||||
|
||||
var memoryStream = new MemoryStream(bytes)
|
||||
{
|
||||
Position = 0
|
||||
};
|
||||
await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
// Handle image/png; charset=utf-8
|
||||
mimeType = mimeType.Split(';').FirstOrDefault();
|
||||
|
||||
await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, null, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
||||
}
|
||||
entity.UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -260,27 +260,25 @@ namespace MediaBrowser.Api.Images
|
||||
/// <returns>Task.</returns>
|
||||
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
|
||||
{
|
||||
using (var result = await _httpClient.GetResponse(new HttpRequestOptions
|
||||
using var result = await _httpClient.GetResponse(new HttpRequestOptions
|
||||
{
|
||||
Url = url,
|
||||
BufferContent = false
|
||||
|
||||
}).ConfigureAwait(false))
|
||||
}).ConfigureAwait(false);
|
||||
var ext = result.ContentType.Split('/').Last();
|
||||
|
||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
using (var stream = result.Content)
|
||||
{
|
||||
var ext = result.ContentType.Split('/').Last();
|
||||
|
||||
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
using (var stream = result.Content)
|
||||
using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||
{
|
||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
File.WriteAllText(pointerCachePath, fullCachePath);
|
||||
using var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true);
|
||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
File.WriteAllText(pointerCachePath, fullCachePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -305,9 +305,16 @@ namespace MediaBrowser.Api
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
|
||||
using (var stream = result.Content)
|
||||
using (var filestream = new FileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, IODefaults.FileStreamBufferSize, true))
|
||||
{
|
||||
await stream.CopyToAsync(filestream).ConfigureAwait(false);
|
||||
using var fileStream = new FileStream(
|
||||
fullCachePath,
|
||||
FileMode.Create,
|
||||
FileAccess.Write,
|
||||
FileShare.Read,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
true);
|
||||
|
||||
await stream.CopyToAsync(fileStream).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
|
||||
|
@ -263,8 +263,7 @@ namespace MediaBrowser.Api
|
||||
item.Overview = request.Overview;
|
||||
item.Genres = request.Genres;
|
||||
|
||||
var episode = item as Episode;
|
||||
if (episode != null)
|
||||
if (item is Episode episode)
|
||||
{
|
||||
episode.AirsAfterSeasonNumber = request.AirsAfterSeasonNumber;
|
||||
episode.AirsBeforeEpisodeNumber = request.AirsBeforeEpisodeNumber;
|
||||
@ -302,14 +301,12 @@ namespace MediaBrowser.Api
|
||||
item.PreferredMetadataCountryCode = request.PreferredMetadataCountryCode;
|
||||
item.PreferredMetadataLanguage = request.PreferredMetadataLanguage;
|
||||
|
||||
var hasDisplayOrder = item as IHasDisplayOrder;
|
||||
if (hasDisplayOrder != null)
|
||||
if (item is IHasDisplayOrder hasDisplayOrder)
|
||||
{
|
||||
hasDisplayOrder.DisplayOrder = request.DisplayOrder;
|
||||
}
|
||||
|
||||
var hasAspectRatio = item as IHasAspectRatio;
|
||||
if (hasAspectRatio != null)
|
||||
if (item is IHasAspectRatio hasAspectRatio)
|
||||
{
|
||||
hasAspectRatio.AspectRatio = request.AspectRatio;
|
||||
}
|
||||
@ -337,16 +334,14 @@ namespace MediaBrowser.Api
|
||||
|
||||
item.ProviderIds = request.ProviderIds;
|
||||
|
||||
var video = item as Video;
|
||||
if (video != null)
|
||||
if (item is Video video)
|
||||
{
|
||||
video.Video3DFormat = request.Video3DFormat;
|
||||
}
|
||||
|
||||
if (request.AlbumArtists != null)
|
||||
{
|
||||
var hasAlbumArtists = item as IHasAlbumArtist;
|
||||
if (hasAlbumArtists != null)
|
||||
if (item is IHasAlbumArtist hasAlbumArtists)
|
||||
{
|
||||
hasAlbumArtists.AlbumArtists = request
|
||||
.AlbumArtists
|
||||
@ -357,8 +352,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
if (request.ArtistItems != null)
|
||||
{
|
||||
var hasArtists = item as IHasArtist;
|
||||
if (hasArtists != null)
|
||||
if (item is IHasArtist hasArtists)
|
||||
{
|
||||
hasArtists.Artists = request
|
||||
.ArtistItems
|
||||
@ -367,20 +361,17 @@ namespace MediaBrowser.Api
|
||||
}
|
||||
}
|
||||
|
||||
var song = item as Audio;
|
||||
if (song != null)
|
||||
if (item is Audio song)
|
||||
{
|
||||
song.Album = request.Album;
|
||||
}
|
||||
|
||||
var musicVideo = item as MusicVideo;
|
||||
if (musicVideo != null)
|
||||
if (item is MusicVideo musicVideo)
|
||||
{
|
||||
musicVideo.Album = request.Album;
|
||||
}
|
||||
|
||||
var series = item as Series;
|
||||
if (series != null)
|
||||
if (item is Series series)
|
||||
{
|
||||
series.Status = GetSeriesStatus(request);
|
||||
|
||||
@ -400,7 +391,6 @@ namespace MediaBrowser.Api
|
||||
}
|
||||
|
||||
return (SeriesStatus)Enum.Parse(typeof(SeriesStatus), item.Status, true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -348,28 +348,19 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
private string[] GetRepresentativeItemTypes(string contentType)
|
||||
{
|
||||
switch (contentType)
|
||||
return contentType switch
|
||||
{
|
||||
case CollectionType.BoxSets:
|
||||
return new string[] { "BoxSet" };
|
||||
case CollectionType.Playlists:
|
||||
return new string[] { "Playlist" };
|
||||
case CollectionType.Movies:
|
||||
return new string[] { "Movie" };
|
||||
case CollectionType.TvShows:
|
||||
return new string[] { "Series", "Season", "Episode" };
|
||||
case CollectionType.Books:
|
||||
return new string[] { "Book" };
|
||||
case CollectionType.Music:
|
||||
return new string[] { "MusicAlbum", "MusicArtist", "Audio", "MusicVideo" };
|
||||
case CollectionType.HomeVideos:
|
||||
case CollectionType.Photos:
|
||||
return new string[] { "Video", "Photo" };
|
||||
case CollectionType.MusicVideos:
|
||||
return new string[] { "MusicVideo" };
|
||||
default:
|
||||
return new string[] { "Series", "Season", "Episode", "Movie" };
|
||||
}
|
||||
CollectionType.BoxSets => new[] {"BoxSet"},
|
||||
CollectionType.Playlists => new[] {"Playlist"},
|
||||
CollectionType.Movies => new[] {"Movie"},
|
||||
CollectionType.TvShows => new[] {"Series", "Season", "Episode"},
|
||||
CollectionType.Books => new[] {"Book"},
|
||||
CollectionType.Music => new[] {"MusicAlbum", "MusicArtist", "Audio", "MusicVideo"},
|
||||
CollectionType.HomeVideos => new[] {"Video", "Photo"},
|
||||
CollectionType.Photos => new[] {"Video", "Photo"},
|
||||
CollectionType.MusicVideos => new[] {"MusicVideo"},
|
||||
_ => new[] {"Series", "Season", "Episode", "Movie"}
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary)
|
||||
@ -397,54 +388,22 @@ namespace MediaBrowser.Api.Library
|
||||
{
|
||||
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
return false;
|
||||
return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
||||
.Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
|
||||
if (metadataOptions.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
|
||||
return metadataOptions.Length == 0
|
||||
|| metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
|
||||
@ -453,50 +412,17 @@ namespace MediaBrowser.Api.Library
|
||||
{
|
||||
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "The Open Movie Database", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
|
||||
&& !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
return false;
|
||||
return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "Emby Designs", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
var metadataOptions = ServerConfigurationManager.Configuration.MetadataOptions
|
||||
@ -561,8 +487,7 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
ImageOption[] defaultImageOptions = null;
|
||||
TypeOptions.DefaultImageOptions.TryGetValue(type, out defaultImageOptions);
|
||||
TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions);
|
||||
|
||||
typeOptions.Add(new LibraryTypeOptions
|
||||
{
|
||||
@ -609,8 +534,6 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
public object Get(GetSimilarItems request)
|
||||
{
|
||||
var user = !request.UserId.Equals(Guid.Empty) ? _userManager.GetUserById(request.UserId) : null;
|
||||
|
||||
var item = string.IsNullOrEmpty(request.Id) ?
|
||||
(!request.UserId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() :
|
||||
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
|
||||
@ -668,7 +591,7 @@ namespace MediaBrowser.Api.Library
|
||||
// ExcludeArtistIds
|
||||
if (!string.IsNullOrEmpty(request.ExcludeArtistIds))
|
||||
{
|
||||
query.ExcludeArtistIds = BaseApiService.GetGuids(request.ExcludeArtistIds);
|
||||
query.ExcludeArtistIds = GetGuids(request.ExcludeArtistIds);
|
||||
}
|
||||
|
||||
List<BaseItem> itemsResult;
|
||||
@ -689,7 +612,6 @@ namespace MediaBrowser.Api.Library
|
||||
var result = new QueryResult<BaseItemDto>
|
||||
{
|
||||
Items = returnList,
|
||||
|
||||
TotalRecordCount = itemsResult.Count
|
||||
};
|
||||
|
||||
@ -919,12 +841,10 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
private BaseItem TranslateParentItem(BaseItem item, User user)
|
||||
{
|
||||
if (item.GetParent() is AggregateFolder)
|
||||
{
|
||||
return _libraryManager.GetUserRootFolder().GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
|
||||
}
|
||||
|
||||
return item;
|
||||
return item.GetParent() is AggregateFolder
|
||||
? _libraryManager.GetUserRootFolder().GetChildren(user, true)
|
||||
.FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path))
|
||||
: item;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -1086,7 +1006,7 @@ namespace MediaBrowser.Api.Library
|
||||
var item = string.IsNullOrEmpty(request.Id)
|
||||
? (!request.UserId.Equals(Guid.Empty)
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: (Folder)_libraryManager.RootFolder)
|
||||
: _libraryManager.RootFolder)
|
||||
: _libraryManager.GetItemById(request.Id);
|
||||
|
||||
if (item == null)
|
||||
@ -1094,18 +1014,13 @@ namespace MediaBrowser.Api.Library
|
||||
throw new ResourceNotFoundException("Item not found.");
|
||||
}
|
||||
|
||||
BaseItem[] themeItems = Array.Empty<BaseItem>();
|
||||
IEnumerable<BaseItem> themeItems;
|
||||
|
||||
while (true)
|
||||
{
|
||||
themeItems = item.GetThemeSongs().ToArray();
|
||||
themeItems = item.GetThemeSongs();
|
||||
|
||||
if (themeItems.Length > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!request.InheritFromParent)
|
||||
if (themeItems.Any() || !request.InheritFromParent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -1119,11 +1034,9 @@ namespace MediaBrowser.Api.Library
|
||||
}
|
||||
|
||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||
|
||||
var dtos = themeItems
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
||||
|
||||
var items = dtos.ToArray();
|
||||
var items = themeItems
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||
.ToArray();
|
||||
|
||||
return new ThemeMediaResult
|
||||
{
|
||||
@ -1140,9 +1053,7 @@ namespace MediaBrowser.Api.Library
|
||||
/// <returns>System.Object.</returns>
|
||||
public object Get(GetThemeVideos request)
|
||||
{
|
||||
var result = GetThemeVideos(request);
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
return ToOptimizedResult(GetThemeVideos(request));
|
||||
}
|
||||
|
||||
public ThemeMediaResult GetThemeVideos(GetThemeVideos request)
|
||||
@ -1152,7 +1063,7 @@ namespace MediaBrowser.Api.Library
|
||||
var item = string.IsNullOrEmpty(request.Id)
|
||||
? (!request.UserId.Equals(Guid.Empty)
|
||||
? _libraryManager.GetUserRootFolder()
|
||||
: (Folder)_libraryManager.RootFolder)
|
||||
: _libraryManager.RootFolder)
|
||||
: _libraryManager.GetItemById(request.Id);
|
||||
|
||||
if (item == null)
|
||||
@ -1160,18 +1071,13 @@ namespace MediaBrowser.Api.Library
|
||||
throw new ResourceNotFoundException("Item not found.");
|
||||
}
|
||||
|
||||
BaseItem[] themeItems = Array.Empty<BaseItem>();
|
||||
IEnumerable<BaseItem> themeItems;
|
||||
|
||||
while (true)
|
||||
{
|
||||
themeItems = item.GetThemeVideos().ToArray();
|
||||
themeItems = item.GetThemeVideos();
|
||||
|
||||
if (themeItems.Length > 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (!request.InheritFromParent)
|
||||
if (themeItems.Any() || !request.InheritFromParent)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@ -1186,10 +1092,9 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||
|
||||
var dtos = themeItems
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
||||
|
||||
var items = dtos.ToArray();
|
||||
var items = themeItems
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))
|
||||
.ToArray();
|
||||
|
||||
return new ThemeMediaResult
|
||||
{
|
||||
|
@ -327,15 +327,11 @@ namespace MediaBrowser.Api.Library
|
||||
|
||||
try
|
||||
{
|
||||
var mediaPath = request.PathInfo;
|
||||
|
||||
if (mediaPath == null)
|
||||
var mediaPath = request.PathInfo ?? new MediaPathInfo
|
||||
{
|
||||
mediaPath = new MediaPathInfo
|
||||
{
|
||||
Path = request.Path
|
||||
};
|
||||
}
|
||||
Path = request.Path
|
||||
};
|
||||
|
||||
_libraryManager.AddMediaPath(request.Name, mediaPath);
|
||||
}
|
||||
finally
|
||||
|
@ -885,11 +885,10 @@ namespace MediaBrowser.Api.LiveTv
|
||||
{
|
||||
// SchedulesDirect requires a SHA1 hash of the user's password
|
||||
// https://github.com/SchedulesDirect/JSON-Service/wiki/API-20141201#obtain-a-token
|
||||
using (SHA1 sha = SHA1.Create())
|
||||
{
|
||||
return Hex.Encode(
|
||||
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
||||
}
|
||||
using SHA1 sha = SHA1.Create();
|
||||
|
||||
return Hex.Encode(
|
||||
sha.ComputeHash(Encoding.UTF8.GetBytes(str)));
|
||||
}
|
||||
|
||||
public void Delete(DeleteListingProvider request)
|
||||
@ -1050,8 +1049,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||
{
|
||||
query.IsSeries = true;
|
||||
|
||||
var series = _libraryManager.GetItemById(request.LibrarySeriesId) as Series;
|
||||
if (series != null)
|
||||
if (_libraryManager.GetItemById(request.LibrarySeriesId) is Series series)
|
||||
{
|
||||
query.Name = series.Name;
|
||||
}
|
||||
|
@ -394,7 +394,7 @@ namespace MediaBrowser.Api.Movies
|
||||
{
|
||||
var people = _libraryManager.GetPeople(new InternalPeopleQuery
|
||||
{
|
||||
PersonTypes = new string[]
|
||||
PersonTypes = new[]
|
||||
{
|
||||
PersonType.Director
|
||||
}
|
||||
|
@ -137,12 +137,9 @@ namespace MediaBrowser.Api.Playback
|
||||
var ext = outputFileExtension.ToLowerInvariant();
|
||||
var folder = ServerConfigurationManager.GetTranscodePath();
|
||||
|
||||
if (EnableOutputInSubFolder)
|
||||
{
|
||||
return Path.Combine(folder, filename, filename + ext);
|
||||
}
|
||||
|
||||
return Path.Combine(folder, filename + ext);
|
||||
return EnableOutputInSubFolder
|
||||
? Path.Combine(folder, filename, filename + ext)
|
||||
: Path.Combine(folder, filename + ext);
|
||||
}
|
||||
|
||||
protected virtual string GetDefaultEncoderPreset()
|
||||
@ -248,14 +245,8 @@ namespace MediaBrowser.Api.Playback
|
||||
if (state.VideoRequest != null
|
||||
&& string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logFilePrefix = "ffmpeg-remux";
|
||||
}
|
||||
else
|
||||
{
|
||||
logFilePrefix = "ffmpeg-directstream";
|
||||
}
|
||||
logFilePrefix = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)
|
||||
? "ffmpeg-remux" : "ffmpeg-directstream";
|
||||
}
|
||||
|
||||
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
|
||||
@ -389,195 +380,181 @@ namespace MediaBrowser.Api.Playback
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
switch (i)
|
||||
{
|
||||
request.DeviceProfileId = val;
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
request.DeviceId = val;
|
||||
}
|
||||
else if (i == 2)
|
||||
{
|
||||
request.MediaSourceId = val;
|
||||
}
|
||||
else if (i == 3)
|
||||
{
|
||||
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else if (i == 4)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.VideoCodec = val;
|
||||
}
|
||||
}
|
||||
else if (i == 5)
|
||||
{
|
||||
request.AudioCodec = val;
|
||||
}
|
||||
else if (i == 6)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 7)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 8)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 9)
|
||||
{
|
||||
request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (i == 10)
|
||||
{
|
||||
request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (i == 11)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 12)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 13)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 14)
|
||||
{
|
||||
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (i == 15)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.Level = val;
|
||||
}
|
||||
}
|
||||
else if (i == 16)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 17)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
else if (i == 18)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.Profile = val;
|
||||
}
|
||||
}
|
||||
else if (i == 19)
|
||||
{
|
||||
// cabac no longer used
|
||||
}
|
||||
else if (i == 20)
|
||||
{
|
||||
request.PlaySessionId = val;
|
||||
}
|
||||
else if (i == 21)
|
||||
{
|
||||
// api_key
|
||||
}
|
||||
else if (i == 22)
|
||||
{
|
||||
request.LiveStreamId = val;
|
||||
}
|
||||
else if (i == 23)
|
||||
{
|
||||
// Duplicating ItemId because of MediaMonkey
|
||||
}
|
||||
else if (i == 24)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 25)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
||||
{
|
||||
if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
|
||||
case 0:
|
||||
request.DeviceProfileId = val;
|
||||
break;
|
||||
case 1:
|
||||
request.DeviceId = val;
|
||||
break;
|
||||
case 2:
|
||||
request.MediaSourceId = val;
|
||||
break;
|
||||
case 3:
|
||||
request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
break;
|
||||
case 4:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.SubtitleMethod = method;
|
||||
videoRequest.VideoCodec = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (i == 26)
|
||||
{
|
||||
request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
else if (i == 27)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 28)
|
||||
{
|
||||
request.Tag = val;
|
||||
}
|
||||
else if (i == 29)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 30)
|
||||
{
|
||||
request.SubtitleCodec = val;
|
||||
}
|
||||
else if (i == 31)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 32)
|
||||
{
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
else if (i == 33)
|
||||
{
|
||||
request.TranscodeReasons = val;
|
||||
|
||||
break;
|
||||
case 5:
|
||||
request.AudioCodec = val;
|
||||
break;
|
||||
case 6:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 7:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 8:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 9:
|
||||
request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case 10:
|
||||
request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case 11:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 12:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 13:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 14:
|
||||
request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case 15:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.Level = val;
|
||||
}
|
||||
|
||||
break;
|
||||
case 16:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 17:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
break;
|
||||
case 18:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.Profile = val;
|
||||
}
|
||||
|
||||
break;
|
||||
case 19:
|
||||
// cabac no longer used
|
||||
break;
|
||||
case 20:
|
||||
request.PlaySessionId = val;
|
||||
break;
|
||||
case 21:
|
||||
// api_key
|
||||
break;
|
||||
case 22:
|
||||
request.LiveStreamId = val;
|
||||
break;
|
||||
case 23:
|
||||
// Duplicating ItemId because of MediaMonkey
|
||||
break;
|
||||
case 24:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.CopyTimestamps = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
break;
|
||||
case 25:
|
||||
if (!string.IsNullOrWhiteSpace(val) && videoRequest != null)
|
||||
{
|
||||
if (Enum.TryParse(val, out SubtitleDeliveryMethod method))
|
||||
{
|
||||
videoRequest.SubtitleMethod = method;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 26:
|
||||
request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
|
||||
break;
|
||||
case 27:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.EnableSubtitlesInManifest = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
break;
|
||||
case 28:
|
||||
request.Tag = val;
|
||||
break;
|
||||
case 29:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.RequireAvc = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
break;
|
||||
case 30:
|
||||
request.SubtitleCodec = val;
|
||||
break;
|
||||
case 31:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.RequireNonAnamorphic = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
break;
|
||||
case 32:
|
||||
if (videoRequest != null)
|
||||
{
|
||||
videoRequest.DeInterlace = string.Equals("true", val, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
break;
|
||||
case 33:
|
||||
request.TranscodeReasons = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -630,14 +607,9 @@ namespace MediaBrowser.Api.Playback
|
||||
throw new ArgumentException("Invalid timeseek header");
|
||||
}
|
||||
int index = value.IndexOf('-');
|
||||
if (index == -1)
|
||||
{
|
||||
value = value.Substring(Npt.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = value.Substring(Npt.Length, index - Npt.Length);
|
||||
}
|
||||
value = index == -1
|
||||
? value.Substring(Npt.Length)
|
||||
: value.Substring(Npt.Length, index - Npt.Length);
|
||||
|
||||
if (value.IndexOf(':') == -1)
|
||||
{
|
||||
@ -856,21 +828,11 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
state.DeviceProfile = DlnaManager.GetProfile(state.Request.DeviceProfileId);
|
||||
}
|
||||
else
|
||||
else if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(state.Request.DeviceId))
|
||||
{
|
||||
var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
|
||||
var caps = DeviceManager.GetCapabilities(state.Request.DeviceId);
|
||||
|
||||
if (caps != null)
|
||||
{
|
||||
state.DeviceProfile = caps.DeviceProfile;
|
||||
}
|
||||
else
|
||||
{
|
||||
state.DeviceProfile = DlnaManager.GetProfile(headers);
|
||||
}
|
||||
}
|
||||
state.DeviceProfile = caps == null ? DlnaManager.GetProfile(headers) : caps.DeviceProfile;
|
||||
}
|
||||
|
||||
var profile = state.DeviceProfile;
|
||||
|
@ -140,7 +140,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
if (isLive)
|
||||
{
|
||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||
|
||||
if (job != null)
|
||||
{
|
||||
@ -156,7 +156,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, baselineStreamBitrate);
|
||||
|
||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType);
|
||||
|
||||
if (job != null)
|
||||
{
|
||||
@ -168,22 +168,19 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
private string GetLivePlaylistText(string path, int segmentLength)
|
||||
{
|
||||
using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var text = reader.ReadToEnd();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
|
||||
var text = reader.ReadToEnd();
|
||||
|
||||
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
|
||||
text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT");
|
||||
|
||||
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||
//text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||
var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength - 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||
//text = text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(CultureInfo.InvariantCulture), newDuration, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, int baselineStreamBitrate)
|
||||
@ -212,29 +209,25 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
try
|
||||
{
|
||||
// Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
|
||||
using (var fileStream = GetPlaylistFileStream(playlist))
|
||||
using var fileStream = GetPlaylistFileStream(playlist);
|
||||
using var reader = new StreamReader(fileStream);
|
||||
var count = 0;
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
using (var reader = new StreamReader(fileStream))
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
while (!reader.EndOfStream)
|
||||
count++;
|
||||
if (count >= segmentCount)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
count++;
|
||||
if (count >= segmentCount)
|
||||
{
|
||||
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
|
||||
return;
|
||||
}
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
@ -247,17 +240,13 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
protected Stream GetPlaylistFileStream(string path)
|
||||
{
|
||||
var tmpPath = path + ".tmp";
|
||||
tmpPath = path;
|
||||
|
||||
try
|
||||
{
|
||||
return new FileStream(tmpPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, IODefaults.FileStreamBufferSize, FileOptions.SequentialScan);
|
||||
}
|
||||
return new FileStream(
|
||||
path,
|
||||
FileMode.Open,
|
||||
FileAccess.Read,
|
||||
FileShare.ReadWrite,
|
||||
IODefaults.FileStreamBufferSize,
|
||||
FileOptions.SequentialScan);
|
||||
}
|
||||
|
||||
protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding)
|
||||
|
@ -284,7 +284,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
//}
|
||||
|
||||
Logger.LogDebug("returning {0} [general case]", segmentPath);
|
||||
job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||
job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
|
||||
return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@ -438,8 +438,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
{
|
||||
var segmentId = "0";
|
||||
|
||||
var segmentRequest = request as GetHlsVideoSegment;
|
||||
if (segmentRequest != null)
|
||||
if (request is GetHlsVideoSegment segmentRequest)
|
||||
{
|
||||
segmentId = segmentRequest.SegmentId;
|
||||
}
|
||||
@ -690,8 +689,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
return false;
|
||||
}
|
||||
|
||||
var request = state.Request as IMasterHlsRequest;
|
||||
if (request != null && !request.EnableAdaptiveBitrateStreaming)
|
||||
if (state.Request is IMasterHlsRequest request && !request.EnableAdaptiveBitrateStreaming)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -936,7 +934,7 @@ namespace MediaBrowser.Api.Playback.Hls
|
||||
|
||||
var framerate = state.VideoStream?.RealFrameRate;
|
||||
|
||||
if (framerate != null && framerate.HasValue)
|
||||
if (framerate.HasValue)
|
||||
{
|
||||
// This is to make sure keyframe interval is limited to our segment,
|
||||
// as forcing keyframes is not enough.
|
||||
|
@ -234,7 +234,7 @@ namespace MediaBrowser.Api.Playback
|
||||
OpenToken = mediaSource.OpenToken
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
info.MediaSources = new MediaSourceInfo[] { openStreamResult.MediaSource };
|
||||
info.MediaSources = new[] { openStreamResult.MediaSource };
|
||||
}
|
||||
}
|
||||
|
||||
@ -289,7 +289,7 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
var mediaSource = await _mediaSourceManager.GetLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
mediaSources = new MediaSourceInfo[] { mediaSource };
|
||||
mediaSources = new[] { mediaSource };
|
||||
}
|
||||
|
||||
if (mediaSources.Length == 0)
|
||||
@ -366,7 +366,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
var options = new VideoOptions
|
||||
{
|
||||
MediaSources = new MediaSourceInfo[] { mediaSource },
|
||||
MediaSources = new[] { mediaSource },
|
||||
Context = EncodingContext.Streaming,
|
||||
DeviceId = auth.DeviceId,
|
||||
ItemId = item.Id,
|
||||
@ -572,8 +572,7 @@ namespace MediaBrowser.Api.Playback
|
||||
{
|
||||
attachment.DeliveryUrl = string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}/Videos/{1}/{2}/Attachments/{3}",
|
||||
ServerConfigurationManager.Configuration.BaseUrl,
|
||||
"/Videos/{0}/{1}/Attachments/{2}",
|
||||
item.Id,
|
||||
mediaSource.Id,
|
||||
attachment.Index);
|
||||
@ -583,7 +582,7 @@ namespace MediaBrowser.Api.Playback
|
||||
private long? GetMaxBitrate(long? clientMaxBitrate, User user)
|
||||
{
|
||||
var maxBitrate = clientMaxBitrate;
|
||||
var remoteClientMaxBitrate = user == null ? 0 : user.Policy.RemoteClientBitrateLimit;
|
||||
var remoteClientMaxBitrate = user?.Policy.RemoteClientBitrateLimit ?? 0;
|
||||
|
||||
if (remoteClientMaxBitrate <= 0)
|
||||
{
|
||||
@ -662,17 +661,9 @@ namespace MediaBrowser.Api.Playback
|
||||
};
|
||||
}).ThenBy(i =>
|
||||
{
|
||||
if (maxBitrate.HasValue)
|
||||
if (maxBitrate.HasValue && i.Bitrate.HasValue)
|
||||
{
|
||||
if (i.Bitrate.HasValue)
|
||||
{
|
||||
if (i.Bitrate.Value <= maxBitrate.Value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 2;
|
||||
}
|
||||
return i.Bitrate.Value <= maxBitrate.Value ? 0 : 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
@ -167,7 +167,7 @@ namespace MediaBrowser.Api.Playback
|
||||
AudioCodec = request.AudioCodec,
|
||||
Protocol = request.TranscodingProtocol,
|
||||
BreakOnNonKeyFrames = request.BreakOnNonKeyFrames,
|
||||
MaxAudioChannels = request.TranscodingAudioChannels.HasValue ? request.TranscodingAudioChannels.Value.ToString(CultureInfo.InvariantCulture) : null
|
||||
MaxAudioChannels = request.TranscodingAudioChannels?.ToString(CultureInfo.InvariantCulture)
|
||||
}
|
||||
};
|
||||
|
||||
@ -300,7 +300,7 @@ namespace MediaBrowser.Api.Playback
|
||||
|
||||
// hls segment container can only be mpegts or fmp4 per ffmpeg documentation
|
||||
// TODO: remove this when we switch back to the segment muxer
|
||||
var supportedHLSContainers = new string[] { "mpegts", "fmp4" };
|
||||
var supportedHLSContainers = new[] { "mpegts", "fmp4" };
|
||||
|
||||
var newRequest = new GetMasterHlsAudioPlaylist
|
||||
{
|
||||
|
@ -243,9 +243,7 @@ namespace MediaBrowser.Api
|
||||
// https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
|
||||
var id = Guid.Parse(GetPathValue(1));
|
||||
|
||||
var plugin = _appHost.Plugins.First(p => p.Id == id) as IHasPluginConfiguration;
|
||||
|
||||
if (plugin == null)
|
||||
if (!(_appHost.Plugins.First(p => p.Id == id) is IHasPluginConfiguration plugin))
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
@ -123,9 +123,7 @@ namespace MediaBrowser.Api.ScheduledTasks
|
||||
{
|
||||
var isHidden = false;
|
||||
|
||||
var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
|
||||
|
||||
if (configurableTask != null)
|
||||
if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
|
||||
{
|
||||
isHidden = configurableTask.IsHidden;
|
||||
}
|
||||
@ -142,9 +140,7 @@ namespace MediaBrowser.Api.ScheduledTasks
|
||||
{
|
||||
var isEnabled = true;
|
||||
|
||||
var configurableTask = i.ScheduledTask as IConfigurableScheduledTask;
|
||||
|
||||
if (configurableTask != null)
|
||||
if (i.ScheduledTask is IConfigurableScheduledTask configurableTask)
|
||||
{
|
||||
isEnabled = configurableTask.IsEnabled;
|
||||
}
|
||||
|
@ -234,59 +234,48 @@ namespace MediaBrowser.Api
|
||||
SetThumbImageInfo(result, item);
|
||||
SetBackdropImageInfo(result, item);
|
||||
|
||||
var program = item as LiveTvProgram;
|
||||
if (program != null)
|
||||
switch (item)
|
||||
{
|
||||
result.StartDate = program.StartDate;
|
||||
}
|
||||
case IHasSeries hasSeries:
|
||||
result.Series = hasSeries.SeriesName;
|
||||
break;
|
||||
case LiveTvProgram program:
|
||||
result.StartDate = program.StartDate;
|
||||
break;
|
||||
case Series series:
|
||||
if (series.Status.HasValue)
|
||||
{
|
||||
result.Status = series.Status.Value.ToString();
|
||||
}
|
||||
|
||||
var hasSeries = item as IHasSeries;
|
||||
if (hasSeries != null)
|
||||
{
|
||||
result.Series = hasSeries.SeriesName;
|
||||
}
|
||||
break;
|
||||
case MusicAlbum album:
|
||||
result.Artists = album.Artists;
|
||||
result.AlbumArtist = album.AlbumArtist;
|
||||
break;
|
||||
case Audio song:
|
||||
result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
|
||||
result.Artists = song.Artists;
|
||||
|
||||
var series = item as Series;
|
||||
if (series != null)
|
||||
{
|
||||
if (series.Status.HasValue)
|
||||
{
|
||||
result.Status = series.Status.Value.ToString();
|
||||
}
|
||||
}
|
||||
MusicAlbum musicAlbum = song.AlbumEntity;
|
||||
|
||||
var album = item as MusicAlbum;
|
||||
if (musicAlbum != null)
|
||||
{
|
||||
result.Album = musicAlbum.Name;
|
||||
result.AlbumId = musicAlbum.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Album = song.Album;
|
||||
}
|
||||
|
||||
if (album != null)
|
||||
{
|
||||
result.Artists = album.Artists;
|
||||
result.AlbumArtist = album.AlbumArtist;
|
||||
}
|
||||
|
||||
var song = item as Audio;
|
||||
|
||||
if (song != null)
|
||||
{
|
||||
result.AlbumArtist = song.AlbumArtists.FirstOrDefault();
|
||||
result.Artists = song.Artists;
|
||||
|
||||
album = song.AlbumEntity;
|
||||
|
||||
if (album != null)
|
||||
{
|
||||
result.Album = album.Name;
|
||||
result.AlbumId = album.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Album = song.Album;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!item.ChannelId.Equals(Guid.Empty))
|
||||
{
|
||||
var channel = _libraryManager.GetItemById(item.ChannelId);
|
||||
result.ChannelName = channel == null ? null : channel.Name;
|
||||
result.ChannelName = channel?.Name;
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -296,12 +285,9 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
var itemWithImage = item.HasImage(ImageType.Thumb) ? item : null;
|
||||
|
||||
if (itemWithImage == null)
|
||||
if (itemWithImage == null && item is Episode)
|
||||
{
|
||||
if (item is Episode)
|
||||
{
|
||||
itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
|
||||
}
|
||||
itemWithImage = GetParentWithImage<Series>(item, ImageType.Thumb);
|
||||
}
|
||||
|
||||
if (itemWithImage == null)
|
||||
@ -323,12 +309,8 @@ namespace MediaBrowser.Api
|
||||
|
||||
private void SetBackdropImageInfo(SearchHint hint, BaseItem item)
|
||||
{
|
||||
var itemWithImage = item.HasImage(ImageType.Backdrop) ? item : null;
|
||||
|
||||
if (itemWithImage == null)
|
||||
{
|
||||
itemWithImage = GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
|
||||
}
|
||||
var itemWithImage = (item.HasImage(ImageType.Backdrop) ? item : null)
|
||||
?? GetParentWithImage<BaseItem>(item, ImageType.Backdrop);
|
||||
|
||||
if (itemWithImage != null)
|
||||
{
|
||||
|
@ -230,17 +230,14 @@ namespace MediaBrowser.Api.Subtitles
|
||||
|
||||
if (string.Equals(request.Format, "vtt", StringComparison.OrdinalIgnoreCase) && request.AddVttTimeMap)
|
||||
{
|
||||
using (var stream = await GetSubtitles(request).ConfigureAwait(false))
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
var text = reader.ReadToEnd();
|
||||
using var stream = await GetSubtitles(request).ConfigureAwait(false);
|
||||
using var reader = new StreamReader(stream);
|
||||
|
||||
text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
|
||||
var text = reader.ReadToEnd();
|
||||
|
||||
return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
|
||||
}
|
||||
}
|
||||
text = text.Replace("WEBVTT", "WEBVTT\nX-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000");
|
||||
|
||||
return ResultFactory.GetResult(Request, text, MimeTypes.GetMimeType("file." + request.Format));
|
||||
}
|
||||
|
||||
return ResultFactory.GetResult(Request, await GetSubtitles(request).ConfigureAwait(false), MimeTypes.GetMimeType("file." + request.Format));
|
||||
|
@ -168,12 +168,9 @@ namespace MediaBrowser.Api.System
|
||||
.First(i => string.Equals(i.Name, request.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// For older files, assume fully static
|
||||
if (file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1))
|
||||
{
|
||||
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.Read);
|
||||
}
|
||||
var fileShare = file.LastWriteTimeUtc < DateTime.UtcNow.AddHours(-1) ? FileShare.Read : FileShare.ReadWrite;
|
||||
|
||||
return ResultFactory.GetStaticFileResult(Request, file.FullName, FileShare.ReadWrite);
|
||||
return ResultFactory.GetStaticFileResult(Request, file.FullName, fileShare);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -92,10 +92,7 @@ namespace MediaBrowser.Api
|
||||
{
|
||||
lock (_timerLock)
|
||||
{
|
||||
if (KillTimer != null)
|
||||
{
|
||||
KillTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
KillTimer?.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,9 +424,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(request.SeasonId))
|
||||
{
|
||||
var season = _libraryManager.GetItemById(new Guid(request.SeasonId)) as Season;
|
||||
|
||||
if (season == null)
|
||||
if (!(_libraryManager.GetItemById(new Guid(request.SeasonId)) is Season season))
|
||||
{
|
||||
throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId);
|
||||
}
|
||||
@ -444,14 +442,7 @@ namespace MediaBrowser.Api
|
||||
|
||||
var season = series.GetSeasons(user, dtoOptions).FirstOrDefault(i => i.IndexNumber == request.Season.Value);
|
||||
|
||||
if (season == null)
|
||||
{
|
||||
episodes = new List<BaseItem>();
|
||||
}
|
||||
else
|
||||
{
|
||||
episodes = ((Season)season).GetEpisodes(user, dtoOptions);
|
||||
}
|
||||
episodes = season == null ? new List<BaseItem>() : ((Season)season).GetEpisodes(user, dtoOptions);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -126,12 +126,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
|
||||
protected override QueryResult<(BaseItem, ItemCounts)> GetItems(GetItemsByName request, InternalItemsQuery query)
|
||||
{
|
||||
if (request is GetAlbumArtists)
|
||||
{
|
||||
return LibraryManager.GetAlbumArtists(query);
|
||||
}
|
||||
|
||||
return LibraryManager.GetArtists(query);
|
||||
return request is GetAlbumArtists ? LibraryManager.GetAlbumArtists(query) : LibraryManager.GetArtists(query);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -82,8 +82,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
{
|
||||
var parent = GetParentItem(request);
|
||||
|
||||
var collectionFolder = parent as IHasCollectionType;
|
||||
if (collectionFolder != null)
|
||||
if (parent is IHasCollectionType collectionFolder)
|
||||
{
|
||||
return collectionFolder.CollectionType;
|
||||
}
|
||||
@ -274,7 +273,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
DtoOptions = dtoOptions
|
||||
};
|
||||
|
||||
Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
|
||||
bool Filter(BaseItem i) => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
|
||||
|
||||
if (parentItem.IsFolder)
|
||||
{
|
||||
@ -284,18 +283,18 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
{
|
||||
items = request.Recursive ?
|
||||
folder.GetRecursiveChildren(user, query).ToList() :
|
||||
folder.GetChildren(user, true).Where(filter).ToList();
|
||||
folder.GetChildren(user, true).Where(Filter).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
items = request.Recursive ?
|
||||
folder.GetRecursiveChildren(filter) :
|
||||
folder.Children.Where(filter).ToList();
|
||||
folder.GetRecursiveChildren(Filter) :
|
||||
folder.Children.Where(Filter).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
items = new[] { parentItem }.Where(filter).ToList();
|
||||
items = new[] { parentItem }.Where(Filter).ToList();
|
||||
}
|
||||
|
||||
var extractedItems = GetAllItems(request, items);
|
||||
@ -346,30 +345,21 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
private bool FilterItem(GetItemsByName request, BaseItem f, string[] excludeItemTypes, string[] includeItemTypes, string[] mediaTypes)
|
||||
{
|
||||
// Exclude item types
|
||||
if (excludeItemTypes.Length > 0)
|
||||
if (excludeItemTypes.Length > 0 && excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (excludeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Include item types
|
||||
if (includeItemTypes.Length > 0)
|
||||
if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!includeItemTypes.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Include MediaTypes
|
||||
if (mediaTypes.Length > 0)
|
||||
if (mediaTypes.Length > 0 && !mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!mediaTypes.Contains(f.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -396,12 +396,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
|
||||
public VideoType[] GetVideoTypes()
|
||||
{
|
||||
if (string.IsNullOrEmpty(VideoTypes))
|
||||
{
|
||||
return Array.Empty<VideoType>();
|
||||
}
|
||||
|
||||
return VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray();
|
||||
return string.IsNullOrEmpty(VideoTypes)
|
||||
? Array.Empty<VideoType>()
|
||||
: VideoTypes.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(v => Enum.Parse<VideoType>(v, true)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -412,12 +410,10 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
{
|
||||
var val = Filters;
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return new ItemFilter[] { };
|
||||
}
|
||||
|
||||
return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (ItemFilter)Enum.Parse(typeof(ItemFilter), v, true)).ToArray();
|
||||
return string.IsNullOrEmpty(val)
|
||||
? Array.Empty<ItemFilter>()
|
||||
: val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(v => Enum.Parse<ItemFilter>(v, true)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -428,12 +424,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
{
|
||||
var val = ImageTypes;
|
||||
|
||||
if (string.IsNullOrEmpty(val))
|
||||
{
|
||||
return new ImageType[] { };
|
||||
}
|
||||
|
||||
return val.Split(',').Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToArray();
|
||||
return string.IsNullOrEmpty(val)
|
||||
? Array.Empty<ImageType>()
|
||||
: val.Split(',').Select(v => Enum.Parse<ImageType>(v, true)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -469,7 +462,9 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
var sortOrderIndex = sortOrders.Length > i ? i : 0;
|
||||
|
||||
var sortOrderValue = sortOrders.Length > sortOrderIndex ? sortOrders[sortOrderIndex] : null;
|
||||
var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase) ? MediaBrowser.Model.Entities.SortOrder.Descending : MediaBrowser.Model.Entities.SortOrder.Ascending;
|
||||
var sortOrder = string.Equals(sortOrderValue, "Descending", StringComparison.OrdinalIgnoreCase)
|
||||
? MediaBrowser.Model.Entities.SortOrder.Descending
|
||||
: MediaBrowser.Model.Entities.SortOrder.Ascending;
|
||||
|
||||
result[i] = new ValueTuple<string, SortOrder>(vals[i], sortOrder);
|
||||
}
|
||||
|
@ -199,21 +199,22 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
item = _libraryManager.GetUserRootFolder();
|
||||
}
|
||||
|
||||
Folder folder = item as Folder;
|
||||
if (folder == null)
|
||||
if (!(item is Folder folder))
|
||||
{
|
||||
folder = _libraryManager.GetUserRootFolder();
|
||||
}
|
||||
|
||||
var hasCollectionType = folder as IHasCollectionType;
|
||||
if (hasCollectionType != null
|
||||
if (folder is IHasCollectionType hasCollectionType
|
||||
&& string.Equals(hasCollectionType.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
request.Recursive = true;
|
||||
request.IncludeItemTypes = "Playlist";
|
||||
}
|
||||
|
||||
bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id);
|
||||
bool isInEnabledFolder = user.Policy.EnabledFolders.Any(i => new Guid(i) == item.Id)
|
||||
// Assume all folders inside an EnabledChannel are enabled
|
||||
|| user.Policy.EnabledChannels.Any(i => new Guid(i) == item.Id);
|
||||
|
||||
var collectionFolders = _libraryManager.GetCollectionFolders(item);
|
||||
foreach (var collectionFolder in collectionFolders)
|
||||
{
|
||||
@ -225,7 +226,7 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
}
|
||||
}
|
||||
|
||||
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder)
|
||||
if (!(item is UserRootFolder) && !user.Policy.EnableAllFolders && !isInEnabledFolder && !user.Policy.EnableAllChannels)
|
||||
{
|
||||
Logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Name, item.Name);
|
||||
return new QueryResult<BaseItem>
|
||||
|
@ -361,7 +361,8 @@ namespace MediaBrowser.Api.UserLibrary
|
||||
|
||||
var dtoOptions = GetDtoOptions(_authContext, request);
|
||||
|
||||
var dtos = item.GetDisplayExtras()
|
||||
var dtos = item
|
||||
.GetExtras(BaseItem.DisplayExtraTypes)
|
||||
.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
|
||||
|
||||
return dtos.ToArray();
|
||||
|
@ -139,17 +139,11 @@ namespace MediaBrowser.Api
|
||||
.ToList();
|
||||
|
||||
var primaryVersion = videosWithVersions.FirstOrDefault();
|
||||
|
||||
if (primaryVersion == null)
|
||||
{
|
||||
primaryVersion = items.OrderBy(i =>
|
||||
{
|
||||
if (i.Video3DFormat.HasValue)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (i.VideoType != Model.Entities.VideoType.VideoFile)
|
||||
if (i.Video3DFormat.HasValue || i.VideoType != Model.Entities.VideoType.VideoFile)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
@ -158,10 +152,7 @@ namespace MediaBrowser.Api
|
||||
})
|
||||
.ThenByDescending(i =>
|
||||
{
|
||||
var stream = i.GetDefaultVideoStream();
|
||||
|
||||
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
|
||||
|
||||
return i.GetDefaultVideoStream()?.Width ?? 0;
|
||||
}).First();
|
||||
}
|
||||
|
||||
|
80
MediaBrowser.Common/Extensions/ProcessExtensions.cs
Normal file
80
MediaBrowser.Common/Extensions/ProcessExtensions.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="Process"/>.
|
||||
/// </summary>
|
||||
public static class ProcessExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronously wait for the process to exit.
|
||||
/// </summary>
|
||||
/// <param name="process">The process to wait for.</param>
|
||||
/// <param name="timeout">The duration to wait before cancelling waiting for the task.</param>
|
||||
/// <returns>True if the task exited normally, false if the timeout elapsed before the process exited.</returns>
|
||||
/// <exception cref="InvalidOperationException">If <see cref="Process.EnableRaisingEvents"/> is not set to true for the process.</exception>
|
||||
public static async Task<bool> WaitForExitAsync(this Process process, TimeSpan timeout)
|
||||
{
|
||||
using (var cancelTokenSource = new CancellationTokenSource(timeout))
|
||||
{
|
||||
return await WaitForExitAsync(process, cancelTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously wait for the process to exit.
|
||||
/// </summary>
|
||||
/// <param name="process">The process to wait for.</param>
|
||||
/// <param name="cancelToken">A <see cref="CancellationToken"/> to observe while waiting for the process to exit.</param>
|
||||
/// <returns>True if the task exited normally, false if cancelled before the process exited.</returns>
|
||||
public static async Task<bool> WaitForExitAsync(this Process process, CancellationToken cancelToken)
|
||||
{
|
||||
if (!process.EnableRaisingEvents)
|
||||
{
|
||||
throw new InvalidOperationException("EnableRisingEvents must be enabled to async wait for a task to exit.");
|
||||
}
|
||||
|
||||
// Add an event handler for the process exit event
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
process.Exited += (sender, args) => tcs.TrySetResult(true);
|
||||
|
||||
// Return immediately if the process has already exited
|
||||
if (process.HasExitedSafe())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Register with the cancellation token then await
|
||||
using (var cancelRegistration = cancelToken.Register(() => tcs.TrySetResult(process.HasExitedSafe())))
|
||||
{
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the associated process has been terminated using
|
||||
/// <see cref="Process.HasExited"/>. This is safe to call even if there is no operating system process
|
||||
/// associated with the <see cref="Process"/>.
|
||||
/// </summary>
|
||||
/// <param name="process">The process to check the exit status for.</param>
|
||||
/// <returns>
|
||||
/// True if the operating system process referenced by the <see cref="Process"/> component has
|
||||
/// terminated, or if there is no associated operating system process; otherwise, false.
|
||||
/// </returns>
|
||||
private static bool HasExitedSafe(this Process process)
|
||||
{
|
||||
try
|
||||
{
|
||||
return process.HasExited;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ namespace MediaBrowser.Common.Updates
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>The available plugin updates.</returns>
|
||||
IAsyncEnumerable<PackageVersionInfo> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
||||
Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Installs the package.
|
||||
|
@ -1326,8 +1326,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
|
||||
// Use some hackery to get the extra type based on foldername
|
||||
Enum.TryParse(extraFolderName.Replace(" ", ""), true, out ExtraType extraType);
|
||||
item.ExtraType = extraType;
|
||||
item.ExtraType = Enum.TryParse(extraFolderName.Replace(" ", string.Empty), true, out ExtraType extraType)
|
||||
? extraType
|
||||
: Model.Entities.ExtraType.Unknown;
|
||||
|
||||
return item;
|
||||
|
||||
@ -2877,14 +2878,29 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The remote trailers.</value>
|
||||
public IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get all extras associated with this item, sorted by <see cref="SortName"/>.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable containing the items.</returns>
|
||||
public IEnumerable<BaseItem> GetExtras()
|
||||
{
|
||||
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
|
||||
return ExtraIds
|
||||
.Select(LibraryManager.GetItemById)
|
||||
.Where(i => i != null)
|
||||
.OrderBy(i => i.SortName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all extras with specific types that are associated with this item.
|
||||
/// </summary>
|
||||
/// <param name="extraTypes">The types of extras to retrieve.</param>
|
||||
/// <returns>An enumerable containing the extras.</returns>
|
||||
public IEnumerable<BaseItem> GetExtras(IReadOnlyCollection<ExtraType> extraTypes)
|
||||
{
|
||||
return ExtraIds.Select(LibraryManager.GetItemById).Where(i => i?.ExtraType != null && extraTypes.Contains(i.ExtraType.Value));
|
||||
return ExtraIds
|
||||
.Select(LibraryManager.GetItemById)
|
||||
.Where(i => i != null)
|
||||
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetTrailers()
|
||||
@ -2895,11 +2911,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
return Array.Empty<BaseItem>();
|
||||
}
|
||||
|
||||
public IEnumerable<BaseItem> GetDisplayExtras()
|
||||
{
|
||||
return GetExtras(DisplayExtraTypes);
|
||||
}
|
||||
|
||||
public virtual bool IsHD => Height >= 720;
|
||||
|
||||
public bool IsShortcut { get; set; }
|
||||
@ -2917,8 +2928,19 @@ namespace MediaBrowser.Controller.Entities
|
||||
return RunTimeTicks ?? 0;
|
||||
}
|
||||
|
||||
// Possible types of extra videos
|
||||
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new[] { Model.Entities.ExtraType.BehindTheScenes, Model.Entities.ExtraType.Clip, Model.Entities.ExtraType.DeletedScene, Model.Entities.ExtraType.Interview, Model.Entities.ExtraType.Sample, Model.Entities.ExtraType.Scene };
|
||||
/// <summary>
|
||||
/// Extra types that should be counted and displayed as "Special Features" in the UI.
|
||||
/// </summary>
|
||||
public static readonly IReadOnlyCollection<ExtraType> DisplayExtraTypes = new HashSet<ExtraType>
|
||||
{
|
||||
Model.Entities.ExtraType.Unknown,
|
||||
Model.Entities.ExtraType.BehindTheScenes,
|
||||
Model.Entities.ExtraType.Clip,
|
||||
Model.Entities.ExtraType.DeletedScene,
|
||||
Model.Entities.ExtraType.Interview,
|
||||
Model.Entities.ExtraType.Sample,
|
||||
Model.Entities.ExtraType.Scene
|
||||
};
|
||||
|
||||
public virtual bool SupportsExternalTransfer => false;
|
||||
|
||||
|
@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of items the query should return.
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
public int Limit { get; set; }
|
||||
|
||||
public Guid ItemId { get; set; }
|
||||
|
@ -78,8 +78,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
if (!string.IsNullOrEmpty(hwType)
|
||||
&& encodingOptions.EnableHardwareEncoding
|
||||
&& codecMap.ContainsKey(hwType)
|
||||
&& CheckVaapi(state, hwType, encodingOptions))
|
||||
&& codecMap.ContainsKey(hwType))
|
||||
{
|
||||
var preferredEncoder = codecMap[hwType];
|
||||
|
||||
@ -93,23 +92,6 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
return defaultEncoder;
|
||||
}
|
||||
|
||||
private bool CheckVaapi(EncodingJobInfo state, string hwType, EncodingOptions encodingOptions)
|
||||
{
|
||||
if (!string.Equals(hwType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// No vaapi requested, return OK.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(encodingOptions.VaapiDevice))
|
||||
{
|
||||
// No device specified, return OK.
|
||||
return true;
|
||||
}
|
||||
|
||||
return IsVaapiSupported(state);
|
||||
}
|
||||
|
||||
private bool IsVaapiSupported(EncodingJobInfo state)
|
||||
{
|
||||
var videoStream = state.VideoStream;
|
||||
@ -424,7 +406,13 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
|
||||
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "aac -strict experimental";
|
||||
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
|
||||
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
|
||||
{
|
||||
return "libfdk_aac";
|
||||
}
|
||||
|
||||
return "aac";
|
||||
}
|
||||
|
||||
if (string.Equals(codec, "mp3", StringComparison.OrdinalIgnoreCase))
|
||||
@ -1605,7 +1593,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
// 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 (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
if ((IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
|| (videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
var videoStream = state.VideoStream;
|
||||
@ -1636,7 +1624,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
var retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay{3}\"";
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
[base]: HW scaling video to OutputSize
|
||||
@ -1648,7 +1636,8 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
|
||||
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
/*
|
||||
[base]: SW scaling video to OutputSize
|
||||
@ -1996,14 +1985,14 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
|
||||
|
||||
// When the input may or may not be hardware VAAPI decodable
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
filters.Add("format=nv12|vaapi");
|
||||
filters.Add("hwupload");
|
||||
}
|
||||
|
||||
// When the input may or may not be hardware QSV decodable
|
||||
else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!hasTextSubs)
|
||||
{
|
||||
@ -2013,25 +2002,29 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
}
|
||||
|
||||
// If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
|
||||
|
||||
else if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && !options.EnableHardwareEncoding)
|
||||
else if (IsVaapiSupported(state) && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var codec = videoStream.Codec.ToLowerInvariant();
|
||||
var pixelFormat = videoStream.PixelFormat.ToLowerInvariant();
|
||||
var isColorDepth10 = !string.IsNullOrEmpty(videoStream.Profile) && (videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
|
||||
|| videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// Assert 10-bit hardware VAAPI decodable
|
||||
if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) != -1
|
||||
&& (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
|
||||
if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
/*
|
||||
Download data from GPU to CPU as p010le format.
|
||||
Colorspace conversion is unnecessary here as libx264 will handle it.
|
||||
If this step is missing, it will fail on AMD but not on intel.
|
||||
*/
|
||||
filters.Add("hwdownload");
|
||||
filters.Add("format=p010le");
|
||||
filters.Add("format=nv12");
|
||||
}
|
||||
|
||||
// Assert 8-bit hardware VAAPI decodable
|
||||
else if ((pixelFormat ?? string.Empty).IndexOf("p10", StringComparison.OrdinalIgnoreCase) == -1)
|
||||
else if (!isColorDepth10)
|
||||
{
|
||||
filters.Add("hwdownload");
|
||||
filters.Add("format=nv12");
|
||||
@ -2077,7 +2070,7 @@ namespace MediaBrowser.Controller.MediaEncoding
|
||||
filters.AddRange(GetScalingFilters(state, inputWidth, inputHeight, threeDFormat, videoDecoder, outputVideoCodec, request.Width, request.Height, request.MaxWidth, request.MaxHeight));
|
||||
|
||||
// Add parameters to use VAAPI with burn-in text subttiles (GH issue #642)
|
||||
if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && options.EnableHardwareEncoding)
|
||||
if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (state.SubtitleStream != null
|
||||
&& state.SubtitleStream.IsTextSubtitleStream
|
||||
|
@ -24,8 +24,7 @@ namespace MediaBrowser.Controller.Persistence
|
||||
/// Deletes the item.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
void DeleteItem(Guid id, CancellationToken cancellationToken);
|
||||
void DeleteItem(Guid id);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the items.
|
||||
@ -169,4 +168,3 @@ namespace MediaBrowser.Controller.Persistence
|
||||
List<string> GetAllArtistNames();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,48 +155,45 @@ namespace MediaBrowser.MediaEncoding.Attachments
|
||||
inputPath,
|
||||
attachmentStreamIndex,
|
||||
outputPath);
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = processArgs,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
};
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
|
||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
int exitCode;
|
||||
|
||||
process.Start();
|
||||
|
||||
var processTcs = new TaskCompletionSource<bool>();
|
||||
process.EnableRaisingEvents = true;
|
||||
process.Exited += (sender, args) => processTcs.TrySetResult(true);
|
||||
var unregister = cancellationToken.Register(() => processTcs.TrySetResult(process.HasExited));
|
||||
var ranToCompletion = await processTcs.Task.ConfigureAwait(false);
|
||||
unregister.Dispose();
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
using (var process = new Process
|
||||
{
|
||||
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
Arguments = processArgs,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
})
|
||||
{
|
||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
process.Start();
|
||||
|
||||
var ranToCompletion = await process.WaitForExitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing attachment extraction process");
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("Killing ffmpeg attachment extraction process");
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing attachment extraction process");
|
||||
}
|
||||
}
|
||||
|
||||
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
|
||||
process.Dispose();
|
||||
|
||||
var failed = false;
|
||||
|
||||
if (exitCode != 0)
|
||||
|
@ -42,6 +42,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
"libvpx",
|
||||
"libvpx-vp9",
|
||||
"aac",
|
||||
"libfdk_aac",
|
||||
"libmp3lame",
|
||||
"libopus",
|
||||
"libvorbis",
|
||||
|
@ -13,7 +13,6 @@ using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.MediaEncoding.Probing;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
@ -22,6 +21,7 @@ using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace MediaBrowser.MediaEncoding.Encoder
|
||||
{
|
||||
@ -38,7 +38,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
private readonly ILogger _logger;
|
||||
private readonly IServerConfigurationManager _configurationManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly Func<ISubtitleEncoder> _subtitleEncoder;
|
||||
private readonly IConfiguration _configuration;
|
||||
@ -58,7 +57,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
ILogger<MediaEncoder> logger,
|
||||
IServerConfigurationManager configurationManager,
|
||||
IFileSystem fileSystem,
|
||||
IProcessFactory processFactory,
|
||||
ILocalizationManager localization,
|
||||
Func<ISubtitleEncoder> subtitleEncoder,
|
||||
IConfiguration configuration,
|
||||
@ -67,7 +65,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
_logger = logger;
|
||||
_configurationManager = configurationManager;
|
||||
_fileSystem = fileSystem;
|
||||
_processFactory = processFactory;
|
||||
_localization = localization;
|
||||
_startupOptionFFmpegPath = startupOptionsFFmpegPath;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
@ -362,30 +359,33 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
: "{0} -i {1} -threads 0 -v warning -print_format json -show_streams -show_format";
|
||||
args = string.Format(args, probeSizeArgument, inputPath).Trim();
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
|
||||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||
RedirectStandardOutput = true,
|
||||
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
|
||||
RedirectStandardOutput = true,
|
||||
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
FileName = _ffprobePath,
|
||||
Arguments = args,
|
||||
|
||||
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
});
|
||||
};
|
||||
|
||||
if (forceEnableLogging)
|
||||
{
|
||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
_logger.LogInformation("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
_logger.LogDebug("{ProcessFileName} {ProcessArgs}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
}
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
@ -571,18 +571,21 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var process = new Process
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false,
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
});
|
||||
};
|
||||
|
||||
_logger.LogDebug("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
_logger.LogDebug("{ProcessFileName} {ProcessArguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
{
|
||||
@ -599,7 +602,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
timeoutMs = DefaultImageExtractionTimeout;
|
||||
}
|
||||
|
||||
ranToCompletion = await process.WaitForExitAsync(timeoutMs).ConfigureAwait(false);
|
||||
ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMilliseconds(timeoutMs)).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
@ -700,23 +703,27 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
}
|
||||
}
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _ffmpegPath,
|
||||
Arguments = args,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false,
|
||||
EnableRaisingEvents = true
|
||||
});
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
};
|
||||
|
||||
_logger.LogInformation(process.StartInfo.FileName + " " + process.StartInfo.Arguments);
|
||||
_logger.LogInformation(processStartInfo.FileName + " " + processStartInfo.Arguments);
|
||||
|
||||
await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
bool ranToCompletion = false;
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = processStartInfo,
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
using (var processWrapper = new ProcessWrapper(process, this))
|
||||
{
|
||||
try
|
||||
@ -732,7 +739,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
while (isResponsive)
|
||||
{
|
||||
if (await process.WaitForExitAsync(30000).ConfigureAwait(false))
|
||||
if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false))
|
||||
{
|
||||
ranToCompletion = true;
|
||||
break;
|
||||
@ -949,14 +956,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
private bool _disposed = false;
|
||||
|
||||
public ProcessWrapper(IProcess process, MediaEncoder mediaEncoder)
|
||||
public ProcessWrapper(Process process, MediaEncoder mediaEncoder)
|
||||
{
|
||||
Process = process;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
Process.Exited += OnProcessExited;
|
||||
}
|
||||
|
||||
public IProcess Process { get; }
|
||||
public Process Process { get; }
|
||||
|
||||
public bool HasExited { get; private set; }
|
||||
|
||||
@ -964,7 +971,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
|
||||
void OnProcessExited(object sender, EventArgs e)
|
||||
{
|
||||
var process = (IProcess)sender;
|
||||
var process = (Process)sender;
|
||||
|
||||
HasExited = true;
|
||||
|
||||
@ -979,7 +986,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
|
||||
DisposeProcess(process);
|
||||
}
|
||||
|
||||
private void DisposeProcess(IProcess process)
|
||||
private void DisposeProcess(Process process)
|
||||
{
|
||||
lock (_mediaEncoder._runningProcessesLock)
|
||||
{
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -12,7 +13,6 @@ using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Model.Diagnostics;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.IO;
|
||||
@ -31,7 +31,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
private readonly IProcessFactory _processFactory;
|
||||
|
||||
public SubtitleEncoder(
|
||||
ILibraryManager libraryManager,
|
||||
@ -40,8 +39,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
IFileSystem fileSystem,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IHttpClient httpClient,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IProcessFactory processFactory)
|
||||
IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_logger = logger;
|
||||
@ -50,7 +48,6 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_httpClient = httpClient;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
_processFactory = processFactory;
|
||||
}
|
||||
|
||||
private string SubtitleCachePath => Path.Combine(_appPaths.DataPath, "subtitles");
|
||||
@ -429,50 +426,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
encodingParam = " -sub_charenc " + encodingParam;
|
||||
}
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
int exitCode;
|
||||
|
||||
using (var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
})
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = string.Format("{0} -i \"{1}\" -c:s srt \"{2}\"", encodingParam, inputPath, outputPath),
|
||||
EnableRaisingEvents = true,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false
|
||||
});
|
||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
_logger.LogInformation("{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting ffmpeg");
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
||||
|
||||
process.Kill();
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing subtitle conversion process");
|
||||
_logger.LogError(ex, "Error starting ffmpeg");
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Killing ffmpeg subtitle conversion process");
|
||||
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing subtitle conversion process");
|
||||
}
|
||||
}
|
||||
|
||||
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
|
||||
process.Dispose();
|
||||
|
||||
var failed = false;
|
||||
|
||||
if (exitCode == -1)
|
||||
@ -578,50 +579,54 @@ namespace MediaBrowser.MediaEncoding.Subtitles
|
||||
outputCodec,
|
||||
outputPath);
|
||||
|
||||
var process = _processFactory.Create(new ProcessOptions
|
||||
int exitCode;
|
||||
|
||||
using (var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = processArgs,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
ErrorDialog = false
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
})
|
||||
{
|
||||
CreateNoWindow = true,
|
||||
UseShellExecute = false,
|
||||
EnableRaisingEvents = true,
|
||||
FileName = _mediaEncoder.EncoderPath,
|
||||
Arguments = processArgs,
|
||||
IsHidden = true,
|
||||
ErrorDialog = false
|
||||
});
|
||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
_logger.LogInformation("{File} {Arguments}", process.StartInfo.FileName, process.StartInfo.Arguments);
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error starting ffmpeg");
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var ranToCompletion = await process.WaitForExitAsync(300000).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
||||
|
||||
process.Kill();
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing subtitle extraction process");
|
||||
_logger.LogError(ex, "Error starting ffmpeg");
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
|
||||
|
||||
if (!ranToCompletion)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogWarning("Killing ffmpeg subtitle extraction process");
|
||||
|
||||
process.Kill();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error killing subtitle extraction process");
|
||||
}
|
||||
}
|
||||
|
||||
exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
}
|
||||
|
||||
var exitCode = ranToCompletion ? process.ExitCode : -1;
|
||||
|
||||
process.Dispose();
|
||||
|
||||
var failed = false;
|
||||
|
||||
if (exitCode == -1)
|
||||
|
@ -1,31 +0,0 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MediaBrowser.Model.Diagnostics
|
||||
{
|
||||
public interface IProcess : IDisposable
|
||||
{
|
||||
event EventHandler Exited;
|
||||
|
||||
void Kill();
|
||||
|
||||
bool WaitForExit(int timeMs);
|
||||
|
||||
Task<bool> WaitForExitAsync(int timeMs);
|
||||
|
||||
int ExitCode { get; }
|
||||
|
||||
void Start();
|
||||
|
||||
StreamWriter StandardInput { get; }
|
||||
|
||||
StreamReader StandardError { get; }
|
||||
|
||||
StreamReader StandardOutput { get; }
|
||||
|
||||
ProcessOptions StartInfo { get; }
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
#nullable disable
|
||||
#pragma warning disable CS1591
|
||||
|
||||
namespace MediaBrowser.Model.Diagnostics
|
||||
{
|
||||
public interface IProcessFactory
|
||||
{
|
||||
IProcess Create(ProcessOptions options);
|
||||
}
|
||||
|
||||
public class ProcessOptions
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
|
||||
public string Arguments { get; set; }
|
||||
|
||||
public string WorkingDirectory { get; set; }
|
||||
|
||||
public bool CreateNoWindow { get; set; }
|
||||
|
||||
public bool UseShellExecute { get; set; }
|
||||
|
||||
public bool EnableRaisingEvents { get; set; }
|
||||
|
||||
public bool ErrorDialog { get; set; }
|
||||
|
||||
public bool RedirectStandardError { get; set; }
|
||||
|
||||
public bool RedirectStandardInput { get; set; }
|
||||
|
||||
public bool RedirectStandardOutput { get; set; }
|
||||
|
||||
public bool IsHidden { get; set; }
|
||||
}
|
||||
}
|
@ -26,12 +26,12 @@ namespace MediaBrowser.Model.Dlna
|
||||
|
||||
public bool SupportsVideoCodec(string codec)
|
||||
{
|
||||
return ContainerProfile.ContainsContainer(VideoCodec, codec);
|
||||
return Type == DlnaProfileType.Video && ContainerProfile.ContainsContainer(VideoCodec, codec);
|
||||
}
|
||||
|
||||
public bool SupportsAudioCodec(string codec)
|
||||
{
|
||||
return ContainerProfile.ContainsContainer(AudioCodec, codec);
|
||||
return (Type == DlnaProfileType.Audio || Type == DlnaProfileType.Video) && ContainerProfile.ContainsContainer(AudioCodec, codec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ namespace MediaBrowser.Model.Entities
|
||||
{
|
||||
public enum ExtraType
|
||||
{
|
||||
Unknown = 0,
|
||||
Clip = 1,
|
||||
Trailer = 2,
|
||||
BehindTheScenes = 3,
|
||||
|
@ -20,7 +20,7 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.1.3" />
|
||||
<PackageReference Include="System.Globalization" Version="4.3.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,190 +0,0 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$MakeNSIS,
|
||||
[switch]$InstallNSIS,
|
||||
[switch]$InstallFFMPEG,
|
||||
[switch]$InstallNSSM,
|
||||
[switch]$SkipJellyfinBuild,
|
||||
[switch]$GenerateZip,
|
||||
[string]$InstallLocation = "./dist/jellyfin-win-nsis",
|
||||
[string]$UXLocation = "../jellyfin-ux",
|
||||
[switch]$InstallTrayApp,
|
||||
[ValidateSet('Debug','Release')][string]$BuildType = 'Release',
|
||||
[ValidateSet('Quiet','Minimal', 'Normal')][string]$DotNetVerbosity = 'Minimal',
|
||||
[ValidateSet('win','win7', 'win8','win81','win10')][string]$WindowsVersion = 'win',
|
||||
[ValidateSet('x64','x86', 'arm', 'arm64')][string]$Architecture = 'x64'
|
||||
)
|
||||
|
||||
$ProgressPreference = 'SilentlyContinue' # Speedup all downloads by hiding progress bars.
|
||||
|
||||
#PowershellCore and *nix check to make determine which temp dir to use.
|
||||
if(($PSVersionTable.PSEdition -eq 'Core') -and (-not $IsWindows)){
|
||||
$TempDir = mktemp -d
|
||||
}else{
|
||||
$TempDir = $env:Temp
|
||||
}
|
||||
#Create staging dir
|
||||
New-Item -ItemType Directory -Force -Path $InstallLocation
|
||||
$ResolvedInstallLocation = Resolve-Path $InstallLocation
|
||||
$ResolvedUXLocation = Resolve-Path $UXLocation
|
||||
|
||||
function Build-JellyFin {
|
||||
if(($Architecture -eq 'arm64') -and ($WindowsVersion -ne 'win10')){
|
||||
Write-Error "arm64 only supported with Windows10 Version"
|
||||
exit
|
||||
}
|
||||
if(($Architecture -eq 'arm') -and ($WindowsVersion -notin @('win10','win81','win8'))){
|
||||
Write-Error "arm only supported with Windows 8 or higher"
|
||||
exit
|
||||
}
|
||||
Write-Verbose "windowsversion-Architecture: $windowsversion-$Architecture"
|
||||
Write-Verbose "InstallLocation: $ResolvedInstallLocation"
|
||||
Write-Verbose "DotNetVerbosity: $DotNetVerbosity"
|
||||
dotnet publish --self-contained -c $BuildType --output $ResolvedInstallLocation -v $DotNetVerbosity -p:GenerateDocumentationFile=false -p:DebugSymbols=false -p:DebugType=none --runtime `"$windowsversion-$Architecture`" Jellyfin.Server
|
||||
}
|
||||
|
||||
function Install-FFMPEG {
|
||||
param(
|
||||
[string]$ResolvedInstallLocation,
|
||||
[string]$Architecture,
|
||||
[string]$FFMPEGVersionX86 = "ffmpeg-4.2.1-win32-shared"
|
||||
)
|
||||
Write-Verbose "Checking Architecture"
|
||||
if($Architecture -notin @('x86','x64')){
|
||||
Write-Warning "No builds available for your selected architecture of $Architecture"
|
||||
Write-Warning "FFMPEG will not be installed"
|
||||
}elseif($Architecture -eq 'x64'){
|
||||
Write-Verbose "Downloading 64 bit FFMPEG"
|
||||
Invoke-WebRequest -Uri https://repo.jellyfin.org/releases/server/windows/ffmpeg/jellyfin-ffmpeg.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose
|
||||
}else{
|
||||
Write-Verbose "Downloading 32 bit FFMPEG"
|
||||
Invoke-WebRequest -Uri https://ffmpeg.zeranoe.com/builds/win32/shared/$FFMPEGVersionX86.zip -UseBasicParsing -OutFile "$tempdir/ffmpeg.zip" | Write-Verbose
|
||||
}
|
||||
|
||||
Expand-Archive "$tempdir/ffmpeg.zip" -DestinationPath "$tempdir/ffmpeg/" -Force | Write-Verbose
|
||||
if($Architecture -eq 'x64'){
|
||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||
Get-ChildItem "$tempdir/ffmpeg" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||
}
|
||||
}else{
|
||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||
Get-ChildItem "$tempdir/ffmpeg/$FFMPEGVersionX86/bin" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||
}
|
||||
}
|
||||
Remove-Item "$tempdir/ffmpeg/" -Recurse -Force -ErrorAction Continue | Write-Verbose
|
||||
Remove-Item "$tempdir/ffmpeg.zip" -Force -ErrorAction Continue | Write-Verbose
|
||||
}
|
||||
|
||||
function Install-NSSM {
|
||||
param(
|
||||
[string]$ResolvedInstallLocation,
|
||||
[string]$Architecture
|
||||
)
|
||||
Write-Verbose "Checking Architecture"
|
||||
if($Architecture -notin @('x86','x64')){
|
||||
Write-Warning "No builds available for your selected architecture of $Architecture"
|
||||
Write-Warning "NSSM will not be installed"
|
||||
}else{
|
||||
Write-Verbose "Downloading NSSM"
|
||||
# [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
# Temporary workaround, file is hosted in an azure blob with a custom domain in front for brevity
|
||||
Invoke-WebRequest -Uri http://files.evilt.win/nssm/nssm-2.24-101-g897c7ad.zip -UseBasicParsing -OutFile "$tempdir/nssm.zip" | Write-Verbose
|
||||
}
|
||||
|
||||
Expand-Archive "$tempdir/nssm.zip" -DestinationPath "$tempdir/nssm/" -Force | Write-Verbose
|
||||
if($Architecture -eq 'x64'){
|
||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||
Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win64" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||
}
|
||||
}else{
|
||||
Write-Verbose "Copying Binaries to Jellyfin location"
|
||||
Get-ChildItem "$tempdir/nssm/nssm-2.24-101-g897c7ad/win32" | ForEach-Object {
|
||||
Copy-Item $_.FullName -Destination $installLocation | Write-Verbose
|
||||
}
|
||||
}
|
||||
Remove-Item "$tempdir/nssm/" -Recurse -Force -ErrorAction Continue | Write-Verbose
|
||||
Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose
|
||||
}
|
||||
|
||||
function Make-NSIS {
|
||||
param(
|
||||
[string]$ResolvedInstallLocation
|
||||
)
|
||||
|
||||
$env:InstallLocation = $ResolvedInstallLocation
|
||||
if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){
|
||||
& "$tempdir/nsis/nsis-3.04/makensis.exe" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi"
|
||||
} else {
|
||||
& "makensis" /D$Architecture /DUXPATH=$ResolvedUXLocation ".\deployment\windows\jellyfin.nsi"
|
||||
}
|
||||
Copy-Item .\deployment\windows\jellyfin_*.exe $ResolvedInstallLocation\..\
|
||||
}
|
||||
|
||||
|
||||
function Install-NSIS {
|
||||
Write-Verbose "Downloading NSIS"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -Uri https://nchc.dl.sourceforge.net/project/nsis/NSIS%203/3.04/nsis-3.04.zip -UseBasicParsing -OutFile "$tempdir/nsis.zip" | Write-Verbose
|
||||
|
||||
Expand-Archive "$tempdir/nsis.zip" -DestinationPath "$tempdir/nsis/" -Force | Write-Verbose
|
||||
}
|
||||
|
||||
function Cleanup-NSIS {
|
||||
Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose
|
||||
Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose
|
||||
}
|
||||
|
||||
function Install-TrayApp {
|
||||
param(
|
||||
[string]$ResolvedInstallLocation,
|
||||
[string]$Architecture
|
||||
)
|
||||
Write-Verbose "Checking Architecture"
|
||||
if($Architecture -ne 'x64'){
|
||||
Write-Warning "No builds available for your selected architecture of $Architecture"
|
||||
Write-Warning "The tray app will not be available."
|
||||
}else{
|
||||
Write-Verbose "Downloading Tray App and copying to Jellyfin location"
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -Uri https://github.com/jellyfin/jellyfin-windows-tray/releases/latest/download/JellyfinTray.exe -UseBasicParsing -OutFile "$installLocation/JellyfinTray.exe" | Write-Verbose
|
||||
}
|
||||
}
|
||||
|
||||
if(-not $SkipJellyfinBuild.IsPresent -and -not ($InstallNSIS -eq $true)){
|
||||
Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture"
|
||||
Build-JellyFin
|
||||
}
|
||||
if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){
|
||||
Write-Verbose "Starting FFMPEG Install"
|
||||
Install-FFMPEG $ResolvedInstallLocation $Architecture
|
||||
}
|
||||
if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){
|
||||
Write-Verbose "Starting NSSM Install"
|
||||
Install-NSSM $ResolvedInstallLocation $Architecture
|
||||
}
|
||||
if($InstallTrayApp.IsPresent -or ($InstallTrayApp -eq $true)){
|
||||
Write-Verbose "Downloading Windows Tray App"
|
||||
Install-TrayApp $ResolvedInstallLocation $Architecture
|
||||
}
|
||||
#Copy-Item .\deployment\windows\install-jellyfin.ps1 $ResolvedInstallLocation\install-jellyfin.ps1
|
||||
#Copy-Item .\deployment\windows\install.bat $ResolvedInstallLocation\install.bat
|
||||
Copy-Item .\LICENSE $ResolvedInstallLocation\LICENSE
|
||||
if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){
|
||||
Write-Verbose "Installing NSIS"
|
||||
Install-NSIS
|
||||
}
|
||||
if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){
|
||||
Write-Verbose "Starting NSIS Package creation"
|
||||
Make-NSIS $ResolvedInstallLocation
|
||||
}
|
||||
if($InstallNSIS.IsPresent -or ($InstallNSIS -eq $true)){
|
||||
Write-Verbose "Cleanup NSIS"
|
||||
Cleanup-NSIS
|
||||
}
|
||||
if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){
|
||||
Compress-Archive -Path $ResolvedInstallLocation -DestinationPath "$ResolvedInstallLocation/jellyfin.zip" -Force
|
||||
}
|
||||
Write-Verbose "Finished"
|
@ -1,2 +0,0 @@
|
||||
dotnet
|
||||
nsis
|
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="confirmation" Title="Confirmation Page" Subtitle="Please confirm your choices for Jellyfin Server installation" GenerateShowFunction="False">
|
||||
<HeaderCustomScript>!include "helpers\StrSlash.nsh"</HeaderCustomScript>
|
||||
<CreateFunctionCustomScript>${StrSlash} '$0' $INSTDIR
|
||||
|
||||
${StrSlash} '$1' $_JELLYFINDATADIR_
|
||||
|
||||
${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \
|
||||
\pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \
|
||||
Installation Folder:\b0 $0\line\b \
|
||||
Service install:\b0 $_INSTALLSERVICE_\line\b \
|
||||
Service start:\b0 $_SERVICESTART_\line\b \
|
||||
Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \
|
||||
Jellyfin Data Folder:\b0 $1\par \
|
||||
\
|
||||
\pard\sa200\sl276\slmult1\f1\lang1043\par \
|
||||
}"</CreateFunctionCustomScript>
|
||||
<RichText Name="ConfirmRichText" Location="12, 12" Size="426, 204" TabIndex="0" ExStyle="WS_EX_STATICEDGE" />
|
||||
</Dialog>
|
@ -1,61 +0,0 @@
|
||||
; =========================================================
|
||||
; This file was generated by NSISDialogDesigner 1.4.4.0
|
||||
; http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
;
|
||||
; Do not edit it manually, use NSISDialogDesigner instead!
|
||||
; Modified by EraYaN (2019-09-01)
|
||||
; =========================================================
|
||||
|
||||
; handle variables
|
||||
Var hCtl_confirmation
|
||||
Var hCtl_confirmation_ConfirmRichText
|
||||
|
||||
; HeaderCustomScript
|
||||
!include "helpers\StrSlash.nsh"
|
||||
|
||||
|
||||
|
||||
; dialog create function
|
||||
Function fnc_confirmation_Create
|
||||
|
||||
; === confirmation (type: Dialog) ===
|
||||
nsDialogs::Create 1018
|
||||
Pop $hCtl_confirmation
|
||||
${If} $hCtl_confirmation == error
|
||||
Abort
|
||||
${EndIf}
|
||||
!insertmacro MUI_HEADER_TEXT "Confirmation Page" "Please confirm your choices for Jellyfin Server installation"
|
||||
|
||||
; === ConfirmRichText (type: RichText) ===
|
||||
nsDialogs::CreateControl /NOUNLOAD "RichEdit20A" ${ES_READONLY}|${WS_VISIBLE}|${WS_CHILD}|${WS_TABSTOP}|${WS_VSCROLL}|${ES_MULTILINE}|${ES_WANTRETURN} ${WS_EX_STATICEDGE} 8u 7u 280u 126u ""
|
||||
Pop $hCtl_confirmation_ConfirmRichText
|
||||
${NSD_AddExStyle} $hCtl_confirmation_ConfirmRichText ${WS_EX_STATICEDGE}
|
||||
|
||||
; CreateFunctionCustomScript
|
||||
${StrSlash} '$0' $INSTDIR
|
||||
|
||||
${StrSlash} '$1' $_JELLYFINDATADIR_
|
||||
|
||||
${If} $_INSTALLSERVICE_ == "Yes"
|
||||
${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \
|
||||
\pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \
|
||||
Installation Folder:\b0 $0\line\b \
|
||||
Service install:\b0 $_INSTALLSERVICE_\line\b \
|
||||
Service start:\b0 $_SERVICESTART_\line\b \
|
||||
Service account:\b0 $_SERVICEACCOUNTTYPE_\line\b \
|
||||
Jellyfin Data Folder:\b0 $1\par \
|
||||
\
|
||||
\pard\sa200\sl276\slmult1\f1\lang1043\par \
|
||||
}"
|
||||
${Else}
|
||||
${NSD_SetText} $hCtl_confirmation_ConfirmRichText "{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1043\viewkind4\uc1 \
|
||||
\pard\widctlpar\sa160\sl252\slmult1\b The installer will proceed based on the following inputs gathered on earlier screens.\par \
|
||||
Installation Folder:\b0 $0\line\b \
|
||||
Service install:\b0 $_INSTALLSERVICE_\line\b \
|
||||
Jellyfin Data Folder:\b0 $1\par \
|
||||
\
|
||||
\pard\sa200\sl276\slmult1\f1\lang1043\par \
|
||||
}"
|
||||
${EndIf}
|
||||
|
||||
FunctionEnd
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="service_config" Title="CoOnfigure the service" Subtitle="This controls what type of access the server gets to this system." GenerateShowFunction="False">
|
||||
<CheckBox Name="StartServiceAfterInstall" Location="12, 192" Size="426, 24" Text="Start Service after Install" Checked="True" TabIndex="0" />
|
||||
<Label Name="LocalSystemAccountLabel" Location="12, 115" Size="426, 46" Text="The Local System account has full access to every resource and file on the system. This can have very real security implications, do not use unless absolutely neseccary." TabIndex="1" />
|
||||
<Label Name="NetworkServiceAccountLabel" Location="12, 39" Size="426, 46" Text="The NetworkService account is a predefined local account used by the service control manager. It is the recommended way to install the Jellyfin Server service." TabIndex="2" />
|
||||
<RadioButton Name="UseLocalSystemAccount" Location="12, 88" Size="426, 24" Text="Use Local System account" TabIndex="3" />
|
||||
<RadioButton Name="UseNetworkServiceAccount" Location="12, 12" Size="426, 24" Text="Use Network Service account (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="4" />
|
||||
</Dialog>
|
@ -1,56 +0,0 @@
|
||||
; =========================================================
|
||||
; This file was generated by NSISDialogDesigner 1.4.4.0
|
||||
; http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
;
|
||||
; Do not edit it manually, use NSISDialogDesigner instead!
|
||||
; =========================================================
|
||||
|
||||
; handle variables
|
||||
Var hCtl_service_config
|
||||
Var hCtl_service_config_StartServiceAfterInstall
|
||||
Var hCtl_service_config_LocalSystemAccountLabel
|
||||
Var hCtl_service_config_NetworkServiceAccountLabel
|
||||
Var hCtl_service_config_UseLocalSystemAccount
|
||||
Var hCtl_service_config_UseNetworkServiceAccount
|
||||
Var hCtl_service_config_Font1
|
||||
|
||||
|
||||
; dialog create function
|
||||
Function fnc_service_config_Create
|
||||
|
||||
; custom font definitions
|
||||
CreateFont $hCtl_service_config_Font1 "Microsoft Sans Serif" "8.25" "700"
|
||||
|
||||
; === service_config (type: Dialog) ===
|
||||
nsDialogs::Create 1018
|
||||
Pop $hCtl_service_config
|
||||
${If} $hCtl_service_config == error
|
||||
Abort
|
||||
${EndIf}
|
||||
!insertmacro MUI_HEADER_TEXT "Configure the service" "This controls what type of access the server gets to this system."
|
||||
|
||||
; === StartServiceAfterInstall (type: Checkbox) ===
|
||||
${NSD_CreateCheckbox} 8u 118u 280u 15u "Start Service after Install"
|
||||
Pop $hCtl_service_config_StartServiceAfterInstall
|
||||
${NSD_Check} $hCtl_service_config_StartServiceAfterInstall
|
||||
|
||||
; === LocalSystemAccountLabel (type: Label) ===
|
||||
${NSD_CreateLabel} 8u 71u 280u 28u "The Local System account has full access to every resource and file on the system. This can have very real security implications, do not use unless absolutely neseccary."
|
||||
Pop $hCtl_service_config_LocalSystemAccountLabel
|
||||
|
||||
; === NetworkServiceAccountLabel (type: Label) ===
|
||||
${NSD_CreateLabel} 8u 24u 280u 28u "The NetworkService account is a predefined local account used by the service control manager. It is the recommended way to install the Jellyfin Server service."
|
||||
Pop $hCtl_service_config_NetworkServiceAccountLabel
|
||||
|
||||
; === UseLocalSystemAccount (type: RadioButton) ===
|
||||
${NSD_CreateRadioButton} 8u 54u 280u 15u "Use Local System account"
|
||||
Pop $hCtl_service_config_UseLocalSystemAccount
|
||||
${NSD_AddStyle} $hCtl_service_config_UseLocalSystemAccount ${WS_GROUP}
|
||||
|
||||
; === UseNetworkServiceAccount (type: RadioButton) ===
|
||||
${NSD_CreateRadioButton} 8u 7u 280u 15u "Use Network Service account (Recommended)"
|
||||
Pop $hCtl_service_config_UseNetworkServiceAccount
|
||||
SendMessage $hCtl_service_config_UseNetworkServiceAccount ${WM_SETFONT} $hCtl_service_config_Font1 0
|
||||
${NSD_Check} $hCtl_service_config_UseNetworkServiceAccount
|
||||
|
||||
FunctionEnd
|
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
This file was created by NSISDialogDesigner 1.4.4.0
|
||||
http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
Do not edit manually!
|
||||
-->
|
||||
<Dialog Name="setuptype" Title="Setup Type" Subtitle="Control how Jellyfin is installed.">
|
||||
<Label Name="InstallasaServiceLabel" Location="12, 115" Size="426, 46" Text="Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares." TabIndex="0" />
|
||||
<RadioButton Name="InstallasaService" Location="12, 88" Size="426, 24" Text="Install as a Service (Advanced Users)" TabIndex="1" />
|
||||
<Label Name="BasicInstallLabel" Location="12, 39" Size="426, 46" Text="The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4." TabIndex="2" />
|
||||
<RadioButton Name="BasicInstall" Location="12, 12" Size="426, 24" Text="Basic Install (Recommended)" Font="Microsoft Sans Serif, 8.25pt, style=Bold" Checked="True" TabIndex="3" />
|
||||
</Dialog>
|
@ -1,50 +0,0 @@
|
||||
; =========================================================
|
||||
; This file was generated by NSISDialogDesigner 1.4.4.0
|
||||
; http://coolsoft.altervista.org/nsisdialogdesigner
|
||||
;
|
||||
; Do not edit it manually, use NSISDialogDesigner instead!
|
||||
; =========================================================
|
||||
|
||||
; handle variables
|
||||
Var hCtl_setuptype
|
||||
Var hCtl_setuptype_InstallasaServiceLabel
|
||||
Var hCtl_setuptype_InstallasaService
|
||||
Var hCtl_setuptype_BasicInstallLabel
|
||||
Var hCtl_setuptype_BasicInstall
|
||||
Var hCtl_setuptype_Font1
|
||||
|
||||
|
||||
; dialog create function
|
||||
Function fnc_setuptype_Create
|
||||
|
||||
; custom font definitions
|
||||
CreateFont $hCtl_setuptype_Font1 "Microsoft Sans Serif" "8.25" "700"
|
||||
|
||||
; === setuptype (type: Dialog) ===
|
||||
nsDialogs::Create 1018
|
||||
Pop $hCtl_setuptype
|
||||
${If} $hCtl_setuptype == error
|
||||
Abort
|
||||
${EndIf}
|
||||
!insertmacro MUI_HEADER_TEXT "Setup Type" "Control how Jellyfin is installed."
|
||||
|
||||
; === InstallasaServiceLabel (type: Label) ===
|
||||
${NSD_CreateLabel} 8u 71u 280u 28u "Install Jellyfin as a service. This method is recommended for Advanced Users. Additional setup is required to access network shares."
|
||||
Pop $hCtl_setuptype_InstallasaServiceLabel
|
||||
|
||||
; === InstallasaService (type: RadioButton) ===
|
||||
${NSD_CreateRadioButton} 8u 54u 280u 15u "Install as a Service (Advanced Users)"
|
||||
Pop $hCtl_setuptype_InstallasaService
|
||||
${NSD_AddStyle} $hCtl_setuptype_InstallasaService ${WS_GROUP}
|
||||
|
||||
; === BasicInstallLabel (type: Label) ===
|
||||
${NSD_CreateLabel} 8u 24u 280u 28u "The basic install will run Jellyfin in your current user account.$\nThis is recommended for new users and those with existing Jellyfin installs older than 10.4."
|
||||
Pop $hCtl_setuptype_BasicInstallLabel
|
||||
|
||||
; === BasicInstall (type: RadioButton) ===
|
||||
${NSD_CreateRadioButton} 8u 7u 280u 15u "Basic Install (Recommended)"
|
||||
Pop $hCtl_setuptype_BasicInstall
|
||||
SendMessage $hCtl_setuptype_BasicInstall ${WM_SETFONT} $hCtl_setuptype_Font1 0
|
||||
${NSD_Check} $hCtl_setuptype_BasicInstall
|
||||
|
||||
FunctionEnd
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user