diff options
18 files changed, 566 insertions, 83 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index c96228f31..c95133dfd 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -28,6 +28,7 @@ - [DrPandemic](https://github.com/drpandemic) - [joern-h](https://github.com/joern-h) - [Khinenw](https://github.com/HelloWorld017) + - [fhriley](https://github.com/fhriley) # Emby Contributors diff --git a/Emby.Server.Implementations/Data/SqliteExtensions.cs b/Emby.Server.Implementations/Data/SqliteExtensions.cs index c0f27b25a..0fb2c10fd 100644 --- a/Emby.Server.Implementations/Data/SqliteExtensions.cs +++ b/Emby.Server.Implementations/Data/SqliteExtensions.cs @@ -18,10 +18,6 @@ namespace Emby.Server.Implementations.Data connection.RunInTransaction(conn => { - //foreach (var query in queries) - //{ - // conn.Execute(query); - //} conn.ExecuteAll(string.Join(";", queries)); }); } @@ -38,7 +34,8 @@ namespace Emby.Server.Implementations.Data public static Guid ReadGuidFromBlob(this IResultSetValue result) { - return new Guid(result.ToBlob()); + // TODO: Remove ToArray when upgrading to netstandard2.1 + return new Guid(result.ToBlob().ToArray()); } public static string ToDateTimeParamValue(this DateTime dateValue) diff --git a/Emby.Server.Implementations/Data/SqliteUserRepository.cs b/Emby.Server.Implementations/Data/SqliteUserRepository.cs index d6d250fb3..11629b389 100644 --- a/Emby.Server.Implementations/Data/SqliteUserRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteUserRepository.cs @@ -35,9 +35,8 @@ namespace Emby.Server.Implementations.Data public string Name => "SQLite"; /// <summary> - /// Opens the connection to the database + /// Opens the connection to the database. /// </summary> - /// <returns>Task.</returns> public void Initialize() { using (var connection = GetConnection()) @@ -180,14 +179,10 @@ namespace Emby.Server.Implementations.Data var id = row[0].ToInt64(); var guid = row[1].ReadGuidFromBlob(); - using (var stream = new MemoryStream(row[2].ToBlob())) - { - stream.Position = 0; - var user = _jsonSerializer.DeserializeFromStream<User>(stream); - user.InternalId = id; - user.Id = guid; - return user; - } + var user = _jsonSerializer.DeserializeFromString<User>(row.GetString(2)); + user.InternalId = id; + user.Id = guid; + return user; } /// <summary> diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index c78d96d4a..b48193c58 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -34,7 +34,7 @@ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="2.2.0" /> <PackageReference Include="ServiceStack.Text.Core" Version="5.6.0" /> <PackageReference Include="sharpcompress" Version="0.23.0" /> - <PackageReference Include="SQLitePCL.pretty.netstandard" Version="1.0.0" /> + <PackageReference Include="SQLitePCL.pretty.netstandard" Version="2.0.1" /> </ItemGroup> <ItemGroup> diff --git a/Emby.Server.Implementations/Library/UserManager.cs b/Emby.Server.Implementations/Library/UserManager.cs index 086527883..a7ea13ca6 100644 --- a/Emby.Server.Implementations/Library/UserManager.cs +++ b/Emby.Server.Implementations/Library/UserManager.cs @@ -558,6 +558,7 @@ namespace Emby.Server.Implementations.Library { _users = new ConcurrentDictionary<Guid, User>( users.Select(x => new KeyValuePair<Guid, User>(x.Id, x))); + return; } var defaultName = Environment.UserName; diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index ec7c026e5..e87283477 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -43,7 +43,7 @@ <PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" /> <PackageReference Include="Serilog.Sinks.File" Version="4.0.0" /> <PackageReference Include="SkiaSharp" Version="1.68.0" /> - <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="1.1.14" /> + <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.0" /> <PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" /> </ItemGroup> diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index 5e4e36a34..594441af0 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -150,14 +150,15 @@ namespace Jellyfin.Server _logger.LogWarning("Failed to enable shared cache for SQLite"); } - using (var appHost = new CoreAppHost( + var appHost = new CoreAppHost( appPaths, _loggerFactory, options, new ManagedFileSystem(_loggerFactory.CreateLogger<ManagedFileSystem>(), appPaths), new NullImageEncoder(), new NetworkManager(_loggerFactory.CreateLogger<NetworkManager>()), - appConfig)) + appConfig); + try { await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); @@ -165,15 +166,20 @@ namespace Jellyfin.Server await appHost.RunStartupTasksAsync().ConfigureAwait(false); - try - { - // Block main thread until shutdown - await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false); - } - catch (TaskCanceledException) - { - // Don't throw on cancellation - } + // Block main thread until shutdown + await Task.Delay(-1, _tokenSource.Token).ConfigureAwait(false); + } + catch (TaskCanceledException) + { + // Don't throw on cancellation + } + catch (Exception ex) + { + _logger.LogCritical(ex, "Error while starting server."); + } + finally + { + appHost?.Dispose(); } if (_restartOnShutdown) diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 1f78f5afc..1054badd9 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -154,7 +154,12 @@ namespace MediaBrowser.Api.Playback return Path.Combine(folder, filename + ext); } - protected virtual string GetDefaultH264Preset() => "superfast"; + protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + protected virtual string GetDefaultEncoderPreset() + { + return "superfast"; + } private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource) { diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 3c6d33e7e..27eb67ee6 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -304,7 +304,7 @@ namespace MediaBrowser.Api.Playback.Hls return args; } - protected override string GetDefaultH264Preset() + protected override string GetDefaultEncoderPreset() { return "veryfast"; } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index fdae59f56..1f24ae881 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -895,9 +895,13 @@ namespace MediaBrowser.Api.Playback.Hls // See if we can save come cpu cycles by avoiding encoding if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) + if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - args += " -bsf:v h264_mp4toannexb"; + string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); + if (!string.IsNullOrEmpty(bitStreamArgs)) + { + args += " " + bitStreamArgs; + } } //args += " -flags -global_header"; @@ -909,7 +913,7 @@ namespace MediaBrowser.Api.Playback.Hls var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg; //args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0"; diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index 3c715c5ad..4a5f4025b 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -92,10 +92,14 @@ namespace MediaBrowser.Api.Playback.Hls if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase)) { // if h264_mp4toannexb is ever added, do not use it for live tv - if (state.VideoStream != null && EncodingHelper.IsH264(state.VideoStream) && + if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - args += " -bsf:v h264_mp4toannexb"; + string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream); + if (!string.IsNullOrEmpty(bitStreamArgs)) + { + args += " " + bitStreamArgs; + } } } else @@ -105,7 +109,7 @@ namespace MediaBrowser.Api.Playback.Hls var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; - args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultH264Preset()) + keyFrameArg; + args += " " + EncodingHelper.GetVideoQualityParam(state, codec, encodingOptions, GetDefaultEncoderPreset()) + keyFrameArg; // Add resolution params, if specified if (!hasGraphicalSubs) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 83a3f3e3c..c15681654 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -77,7 +77,8 @@ namespace MediaBrowser.Api.Playback.Progressive { var videoCodec = state.VideoRequest.VideoCodec; - if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase)) { return ".ts"; } diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index ab19fdc26..cfc8a283d 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Api.Playback.Progressive protected override string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding) { - return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultH264Preset()); + return EncodingHelper.GetProgressiveVideoFullCommandLine(state, encodingOptions, outputPath, GetDefaultEncoderPreset()); } } } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b9ccc93ef..87874001a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -34,8 +34,16 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetH264Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) { - var defaultEncoder = "libx264"; + return GetH264OrH265Encoder("libx264", "h264", state, encodingOptions); + } + public string GetH265Encoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + return GetH264OrH265Encoder("libx265", "hevc", state, encodingOptions); + } + + private string GetH264OrH265Encoder(string defaultEncoder, string hwEncoder, EncodingJobInfo state, EncodingOptions encodingOptions) + { // Only use alternative encoders for video files. // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully // Since transcoding of folder rips is expiremental anyway, it's not worth adding additional variables such as this. @@ -45,14 +53,14 @@ namespace MediaBrowser.Controller.MediaEncoding var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase) { - {"qsv", "h264_qsv"}, - {"h264_qsv", "h264_qsv"}, - {"nvenc", "h264_nvenc"}, - {"amf", "h264_amf"}, - {"omx", "h264_omx"}, - {"h264_v4l2m2m", "h264_v4l2m2m"}, - {"mediacodec", "h264_mediacodec"}, - {"vaapi", "h264_vaapi"} + {"qsv", hwEncoder + "_qsv"}, + {hwEncoder + "_qsv", hwEncoder + "_qsv"}, + {"nvenc", hwEncoder + "_nvenc"}, + {"amf", hwEncoder + "_amf"}, + {"omx", hwEncoder + "_omx"}, + {hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m"}, + {"mediacodec", hwEncoder + "_mediacodec"}, + {"vaapi", hwEncoder + "_vaapi"} }; if (!string.IsNullOrEmpty(hwType) @@ -119,6 +127,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (!string.IsNullOrEmpty(codec)) { + if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || + string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)) + { + return GetH265Encoder(state, encodingOptions); + } if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase)) { return GetH264Encoder(state, encodingOptions); @@ -476,6 +489,30 @@ namespace MediaBrowser.Controller.MediaEncoding codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; } + public bool IsH265(MediaStream stream) + { + var codec = stream.Codec ?? string.Empty; + + return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 || + codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; + } + + public string GetBitStreamArgs(MediaStream stream) + { + if (IsH264(stream)) + { + return "-bsf:v h264_mp4toannexb"; + } + else if (IsH265(stream)) + { + return "-bsf:v hevc_mp4toannexb"; + } + else + { + return null; + } + } + public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) { var bitrate = state.OutputVideoBitrate; @@ -494,7 +531,8 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format(" -b:v {0}", bitrate.Value.ToString(_usCulture)); } - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { // h264 return string.Format(" -maxrate {0} -bufsize {1}", @@ -515,8 +553,10 @@ namespace MediaBrowser.Controller.MediaEncoding { // Clients may direct play higher than level 41, but there's no reason to transcode higher if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel) - && string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) - && requestLevel > 41) + && requestLevel > 41 + && (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "h265", StringComparison.OrdinalIgnoreCase) + || string.Equals(videoCodec, "hevc", StringComparison.OrdinalIgnoreCase))) { return "41"; } @@ -620,49 +660,53 @@ namespace MediaBrowser.Controller.MediaEncoding /// <summary> /// Gets the video bitrate to specify on the command line /// </summary> - public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultH264Preset) + public string GetVideoQualityParam(EncodingJobInfo state, string videoEncoder, EncodingOptions encodingOptions, string defaultPreset) { var param = string.Empty; var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); + var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase); - if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265) { - if (!string.IsNullOrEmpty(encodingOptions.H264Preset)) + if (!string.IsNullOrEmpty(encodingOptions.EncoderPreset)) { - param += "-preset " + encodingOptions.H264Preset; + param += "-preset " + encodingOptions.EncoderPreset; } else { - param += "-preset " + defaultH264Preset; + param += "-preset " + defaultPreset; } - if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51) + int encodeCrf = encodingOptions.H264Crf; + if (isLibX265) { - param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture); + encodeCrf = encodingOptions.H265Crf; + } + if (encodeCrf >= 0 && encodeCrf <= 51) + { + param += " -crf " + encodeCrf.ToString(CultureInfo.InvariantCulture); } else { - param += " -crf 23"; + string defaultCrf = "23"; + if (isLibX265) + { + defaultCrf = "28"; + } + param += " -crf " + defaultCrf; } } - else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) - { - param += "-preset fast"; - - param += " -crf 28"; - } - // h264 (h264_qsv) else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { string[] valid_h264_qsv = { "veryslow", "slower", "slow", "medium", "fast", "faster", "veryfast" }; - if (valid_h264_qsv.Contains(encodingOptions.H264Preset, StringComparer.OrdinalIgnoreCase)) + if (valid_h264_qsv.Contains(encodingOptions.EncoderPreset, StringComparer.OrdinalIgnoreCase)) { - param += "-preset " + encodingOptions.H264Preset; + param += "-preset " + encodingOptions.EncoderPreset; } else { @@ -674,9 +718,10 @@ namespace MediaBrowser.Controller.MediaEncoding } // h264 (h264_nvenc) - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - switch (encodingOptions.H264Preset) + switch (encodingOptions.EncoderPreset) { case "veryslow": @@ -790,7 +835,8 @@ namespace MediaBrowser.Controller.MediaEncoding // h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format // also needed for libx264 due to https://trac.ffmpeg.org/ticket/3307 if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) || - string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase)) + string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) { switch (level) { @@ -827,9 +873,10 @@ namespace MediaBrowser.Controller.MediaEncoding } } // nvenc doesn't decode with param -level set ?! - else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) || + string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)) { - //param += ""; + // todo param += ""; } else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)) { @@ -842,6 +889,11 @@ namespace MediaBrowser.Controller.MediaEncoding param += " -x264opts:0 subme=0:me_range=4:rc_lookahead=10:me=dia:no_chroma_me:8x8dct=0:partitions=none"; } + if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase)) + { + // todo + } + if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && @@ -1743,7 +1795,8 @@ namespace MediaBrowser.Controller.MediaEncoding var videoStream = state.VideoStream; - if (state.DeInterlace("h264", true) && !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) + if ((state.DeInterlace("h264", true) || state.DeInterlace("h265", true) || state.DeInterlace("hevc", true)) && + !string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)) { var inputFramerate = videoStream == null ? null : videoStream.RealFrameRate; @@ -2404,7 +2457,7 @@ namespace MediaBrowser.Controller.MediaEncoding return args; } - public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultH264Preset) + public string GetProgressiveVideoFullCommandLine(EncodingJobInfo state, EncodingOptions encodingOptions, string outputPath, string defaultPreset) { // Get the output codec name var videoCodec = GetVideoEncoder(state, encodingOptions); @@ -2428,7 +2481,7 @@ namespace MediaBrowser.Controller.MediaEncoding GetInputArgument(state, encodingOptions), keyFrame, GetMapArgs(state), - GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultH264Preset), + GetProgressiveVideoArguments(state, encodingOptions, videoCodec, defaultPreset), threads, GetProgressiveVideoAudioArguments(state, encodingOptions), GetSubtitleEmbedArguments(state), @@ -2453,7 +2506,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Empty; } - public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultH264Preset) + public string GetProgressiveVideoArguments(EncodingJobInfo state, EncodingOptions encodingOptions, string videoCodec, string defaultPreset) { var args = "-codec:v:0 " + videoCodec; @@ -2464,11 +2517,15 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(videoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { - if (state.VideoStream != null && IsH264(state.VideoStream) && + if (state.VideoStream != null && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase)) { - args += " -bsf:v h264_mp4toannexb"; + string bitStreamArgs = GetBitStreamArgs(state.VideoStream); + if (!string.IsNullOrEmpty(bitStreamArgs)) + { + args += " " + bitStreamArgs; + } } if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps) @@ -2514,7 +2571,7 @@ namespace MediaBrowser.Controller.MediaEncoding args += GetGraphicalSubtitleParam(state, encodingOptions, videoCodec); } - var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultH264Preset); + var qualityParam = GetVideoQualityParam(state, videoCodec, encodingOptions, defaultPreset); if (!string.IsNullOrEmpty(qualityParam)) { diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs index be97c75a2..d64feb2f7 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs @@ -118,14 +118,14 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets or sets the profile. /// </summary> /// <value>The profile.</value> - [ApiMember(Name = "Profile", Description = "Optional. Specify a specific h264 profile, e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Profile", Description = "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Profile { get; set; } /// <summary> /// Gets or sets the level. /// </summary> /// <value>The level.</value> - [ApiMember(Name = "Level", Description = "Optional. Specify a level for the h264 profile, e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "Level", Description = "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string Level { get; set; } /// <summary> @@ -212,7 +212,7 @@ namespace MediaBrowser.Controller.MediaEncoding /// Gets or sets the video codec. /// </summary> /// <value>The video codec.</value> - [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + [ApiMember(Name = "VideoCodec", Description = "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string VideoCodec { get; set; } public string SubtitleCodec { get; set; } diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index 285ff4ba5..9ae10d980 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -18,7 +18,8 @@ namespace MediaBrowser.Model.Configuration public string EncoderAppPathDisplay { get; set; } public string VaapiDevice { get; set; } public int H264Crf { get; set; } - public string H264Preset { get; set; } + public int H265Crf { get; set; } + public string EncoderPreset { get; set; } public string DeinterlaceMethod { get; set; } public bool EnableHardwareEncoding { get; set; } public bool EnableSubtitleExtraction { get; set; } @@ -34,6 +35,7 @@ namespace MediaBrowser.Model.Configuration // This is a DRM device that is almost guaranteed to be there on every intel platform, plus it's the default one in ffmpeg if you don't specify anything VaapiDevice = "/dev/dri/renderD128"; H264Crf = 23; + H265Crf = 28; EnableHardwareEncoding = true; EnableSubtitleExtraction = true; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; diff --git a/deployment/windows/build-jellyfin.ps1 b/deployment/windows/build-jellyfin.ps1 index 2999912b3..aaa4bcf6c 100644 --- a/deployment/windows/build-jellyfin.ps1 +++ b/deployment/windows/build-jellyfin.ps1 @@ -1,5 +1,6 @@ [CmdletBinding()] param( + [switch]$MakeNSIS, [switch]$InstallFFMPEG, [switch]$InstallNSSM, [switch]$GenerateZip, @@ -96,6 +97,24 @@ function Install-NSSM { Remove-Item "$tempdir/nssm.zip" -Force -ErrorAction Continue | Write-Verbose } +function Make-NSIS { + param( + [string]$InstallLocation + ) + 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 + $env:InstallLocation = $InstallLocation + & "$tempdir/nsis/nsis-3.04/makensis.exe" ".\deployment\windows\jellyfin.nsi" + Copy-Item .\deployment\windows\jellyfin_*.exe $InstallLocation\..\ + + Remove-Item "$tempdir/nsis/" -Recurse -Force -ErrorAction Continue | Write-Verbose + Remove-Item "$tempdir/nsis.zip" -Force -ErrorAction Continue | Write-Verbose +} + + Write-Verbose "Starting Build Process: Selected Environment is $WindowsVersion-$Architecture" Build-JellyFin if($InstallFFMPEG.IsPresent -or ($InstallFFMPEG -eq $true)){ @@ -108,6 +127,11 @@ if($InstallNSSM.IsPresent -or ($InstallNSSM -eq $true)){ } Copy-Item .\deployment\windows\install-jellyfin.ps1 $InstallLocation\install-jellyfin.ps1 Copy-Item .\deployment\windows\install.bat $InstallLocation\install.bat +Copy-Item .\LICENSE $InstallLocation\LICENSE +if($MakeNSIS.IsPresent -or ($MakeNSIS -eq $true)){ + Write-Verbose "Starting NSIS Package creation" + Make-NSIS $InstallLocation +} if($GenerateZip.IsPresent -or ($GenerateZip -eq $true)){ Compress-Archive -Path $InstallLocation -DestinationPath "$InstallLocation/jellyfin.zip" -Force } diff --git a/deployment/windows/jellyfin.nsi b/deployment/windows/jellyfin.nsi new file mode 100644 index 000000000..a374ebe45 --- /dev/null +++ b/deployment/windows/jellyfin.nsi @@ -0,0 +1,386 @@ +; Shows a lot of debug information while compiling +; This can be removed once stable. +!verbose 4 +;-------------------------------- +!define SF_USELECTED 0 ; used to check selected options status, rest are inherited from Sections.nsh + + !include "MUI2.nsh" + !include "Sections.nsh" + !include "LogicLib.nsh" + +; Global variables that we'll use + Var _JELLYFINVERSION_ + Var _JELLYFINDATADIR_ + Var _INSTALLSERVICE_ + Var _SERVICESTART_ + Var _LOCALSYSTEMACCOUNT_ + Var _EXISTINGINSTALLATION_ + Var _EXISTINGSERVICE_ + Var _CUSTOMDATAFOLDER_ + +!if ${NSIS_PTR_SIZE} > 4 + !define BITS 64 + !define NAMESUFFIX " (64 bit)" +!else + !define BITS 32 + !define NAMESUFFIX "" +!endif + +;-------------------------------- + + !define REG_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\Jellyfin" ;Registry to show up in Add/Remove Programs + + !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ ;Align installer version with jellyfin.dll version + Name "Jellyfin Server ${ver_1}.${ver_2}.${ver_3}" ; This is referred in various header text labels + OutFile "jellyfin_${ver_1}.${ver_2}.${ver_3}_windows.exe" ; Naming convention jellyfin_{version}_windows-{arch].exe + BrandingText "Jellyfin Server ${ver_1}.${ver_2}.${ver_3} Installer" ; This shows in just over the buttons + +; installer attributes, these show up in details tab on installer properties + VIProductVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIProductVersion format, should be X.X.X.X + VIFileVersion "${ver_1}.${ver_2}.${ver_3}.0" ; VIFileVersion format, should be X.X.X.X + VIAddVersionKey "ProductName" "Jellyfin Server" + VIAddVersionKey "FileVersion" "${ver_1}.${ver_2}.${ver_3}.0" + VIAddVersionKey "LegalCopyright" "Jellyfin, Free Software Media System" + VIAddVersionKey "FileDescription" "Jellyfin Server" + +;TODO, check defaults + InstallDir "$PROGRAMFILES\Jellyfin" ;Default installation folder + InstallDirRegKey HKLM "Software\Jellyfin" "InstallFolder" ;Read the registry for install folder, + + RequestExecutionLevel admin ; ask it upfront for service control, and installing in priv folders + + CRCCheck on ; make sure the installer wasn't corrupted while downloading + + !define MUI_ABORTWARNING ;Prompts user in case of aborting install + +; TODO: Replace with nice Jellyfin Icons + !define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\nsis3-install.ico" ; Installer Icon + !define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\nsis3-uninstall.ico" ; Uninstaller Icon + + !define MUI_HEADERIMAGE + !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis3-branding.bmp" + !define MUI_WELCOMEFINISHPAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Wizard\nsis3-branding.bmp" + +;-------------------------------- +;Pages + +; Welcome Page + !define MUI_WELCOMEPAGE_TEXT "The installer will ask for details to install Jellyfin Server.$\r$\n$\r$\n$\r$\n\ + ADVANCED:$\r$\n\ + The default service install uses Network Service account and is sufficient for most users. $\r$\n$\r$\n\ + You can choose to install using Local System account under Advanced options. This also affects where Jellyfin Server and Jellyfin data can be installed. The installer will NOT check this, you should know what you are doing.$\r$\n$\r$\n\ + You can choose the folder for Jellyfin Metadata under advanced options based on your needs." + !insertmacro MUI_PAGE_WELCOME +; License Page + !insertmacro MUI_PAGE_LICENSE "$%InstallLocation%\LICENSE" ; picking up generic GPL +; Components Page + !define MUI_COMPONENTSPAGE_SMALLDESC + !insertmacro MUI_PAGE_COMPONENTS + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideInstallDirectoryPage ; Controls when to hide / show + !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Install folder" ; shows just above the folder selection dialog + !insertmacro MUI_PAGE_DIRECTORY + +; Metadata folder Page + !define MUI_PAGE_CUSTOMFUNCTION_PRE HideDataDirectoryPage ; Controls when to hide / show + !define MUI_PAGE_HEADER_SUBTEXT "Choose the folder in which to install the Jellyfin Server metadata." + !define MUI_DIRECTORYPAGE_TEXT_TOP "The installer will set the following folder for Jellyfin Server metadata. To install in a differenct folder, click Browse and select another folder. Please make sure the folder exists. Click Next to continue." + !define MUI_DIRECTORYPAGE_TEXT_DESTINATION "Metadata folder" + !define MUI_DIRECTORYPAGE_VARIABLE $_JELLYFINDATADIR_ + !insertmacro MUI_PAGE_DIRECTORY + +; Confirmation Page + Page custom ConfirmationPage ; just letting the user know what they chose to install + +; Actual Installion Page + !insertmacro MUI_PAGE_INSTFILES + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + !insertmacro MUI_UNPAGE_FINISH + +;-------------------------------- +;Languages; Add more languages later here if needed + !insertmacro MUI_LANGUAGE "English" + +;-------------------------------- +;Installer Sections +Section "Jellyfin Server (required)" InstallJellyfin + SectionIn RO ; Mandatory section, isn't this the whole purpose to run the installer. + + StrCmp "$_EXISTINGINSTALLATION_" "YES" RunUninstaller CarryOn ; Silently uninstall in case of previous installation + + RunUninstaller: + DetailPrint "Looking for uninstaller at $INSTDIR" + FindFirst $0 $1 "$INSTDIR\Uninstall.exe" + FindClose $0 + StrCmp $1 "" CarryOn ; the registry key was there but uninstaller was not found + + DetailPrint "Silently running the uninstaller at $INSTDIR" + ExecWait '"$INSTDIR\Uninstall.exe" /S _?=$INSTDIR' $0 + DetailPrint "Uninstall finished, $0" + + CarryOn: + + SetOutPath "$INSTDIR" + + File /r $%InstallLocation%\* + +; Write the InstallFolder, DataFolder, Network Service info into the registry for later use + WriteRegExpandStr HKLM "Software\Jellyfin" "InstallFolder" "$INSTDIR" + WriteRegExpandStr HKLM "Software\Jellyfin" "DataFolder" "$_JELLYFINDATADIR_" + WriteRegStr HKLM "Software\Jellyfin" "LocalSystemAccount" "$_LOCALSYSTEMACCOUNT_" + + !getdllversion "$%InstallLocation%\jellyfin.dll" ver_ + StrCpy $_JELLYFINVERSION_ "${ver_1}.${ver_2}.${ver_3}" ; + +; Write the uninstall keys for Windows + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayName" "Jellyfin $_JELLYFINVERSION_" + WriteRegExpandStr HKLM "${REG_UNINST_KEY}" "UninstallString" '"$INSTDIR\Uninstall.exe"' + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayIcon" '"$INSTDIR\Jellyfin.exe",0' + WriteRegStr HKLM "${REG_UNINST_KEY}" "Publisher" "The Jellyfin project" + WriteRegStr HKLM "${REG_UNINST_KEY}" "URLInfoAbout" "https://jellyfin.github.io/" + WriteRegStr HKLM "${REG_UNINST_KEY}" "DisplayVersion" "$_JELLYFINVERSION_" + WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoModify" 1 + WriteRegDWORD HKLM "${REG_UNINST_KEY}" "NoRepair" 1 + +;Create uninstaller + WriteUninstaller "$INSTDIR\Uninstall.exe" + +SectionEnd + +Section "Jellyfin Service" InstallService + + ExecWait '"$INSTDIR\nssm.exe" install Jellyfin "$INSTDIR\jellyfin.exe" --datadir "$_JELLYFINDATADIR_"' $0 + DetailPrint "Jellyfin Service install, $0" + + Sleep 3000 ; Give time for Windows to catchup + + ExecWait '"$INSTDIR\nssm.exe" set Jellyfin Start SERVICE_DELAYED_AUTO_START' $0 + DetailPrint "Jellyfin Service setting, $0" + + Sleep 3000 + ${If} $_LOCALSYSTEMACCOUNT_ == "NO" ; the default install using NSSM is Local System + DetailPrint "Attempting to change service account to Network Service" + ExecWait '"$INSTDIR\nssm.exe" set Jellyfin Objectname "Network Service"' $0 + DetailPrint "Jellyfin service account change, $0" + ${EndIf} + +SectionEnd + +Section "Start Jellyfin service after install" StartService + + ExecWait '"$INSTDIR\nssm.exe" start Jellyfin' $0 + DetailPrint "Jellyfin service start, $0" + +SectionEnd + +SectionGroup "Advanced" Advanced +Section /o "Use Local System account" LocalSystemAccount + ; The section is for user choice, nothing to do here +SectionEnd +Section /o "Custom Jellyfin metadata folder" CustomDataFolder + ; The section is for user choice, nothing to do here +SectionEnd +SectionGroupEnd + + +;-------------------------------- +;Descriptions + +;Language strings + LangString DESC_InstallJellyfin ${LANG_ENGLISH} "Install Jellyfin Server" + LangString DESC_InstallService ${LANG_ENGLISH} "Install As a Service" + LangString DESC_StartService ${LANG_ENGLISH} "Start Jellyfin service after Install" + LangString DESC_LocalSystemAccount ${LANG_ENGLISH} "Use Local System account to start windows service" + LangString DESC_CustomDataFolder ${LANG_ENGLISH} "Choose Jellyfin Server metadata folder in subsequent steps" + +;Assign language strings to sections + !insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN + !insertmacro MUI_DESCRIPTION_TEXT ${InstallJellyfin} $(DESC_InstallJellyfin) + !insertmacro MUI_DESCRIPTION_TEXT ${InstallService} $(DESC_InstallService) + !insertmacro MUI_DESCRIPTION_TEXT ${StartService} $(DESC_StartService) + !insertmacro MUI_DESCRIPTION_TEXT ${LocalSystemAccount} $(DESC_LocalSystemAccount) + !insertmacro MUI_DESCRIPTION_TEXT ${CustomDataFolder} $(DESC_CustomDataFolder) + !insertmacro MUI_FUNCTION_DESCRIPTION_END + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + + ReadRegStr $INSTDIR HKLM "Software\Jellyfin" "InstallFolder" ; read the installation folder + ReadRegStr $_JELLYFINDATADIR_ HKLM "Software\Jellyfin" "DataFolder" ; read the metadata folder + + DetailPrint "Jellyfin Install location : $INSTDIR" + DetailPrint "Jellyfin data folder : $_JELLYFINDATADIR_" + + MessageBox MB_YESNO|MB_ICONINFORMATION "Do you want to retain Jellyfin metadata folder? The media will not be touched. $\r$\nIf unsure choose YES." /SD IDYES IDYES PreserveData + + RMDir /r /REBOOTOK "$_JELLYFINDATADIR_" + + PreserveData: + + DetailPrint "Attempting to stop Jellyfin Server" + ExecWait '"$INSTDIR\nssm.exe" stop Jellyfin' $0 + DetailPrint "Jellyfin service stop, $0" + DetailPrint "Attempting to remove Jellyfin service" + ExecWait '"$INSTDIR\nssm.exe" remove Jellyfin confirm' $0 + DetailPrint "Jellyfin Service remove, $0" + + Delete "$INSTDIR\Uninstall.exe" + + RMDir /r /REBOOTOK "$INSTDIR" + + DeleteRegKey HKLM "Software\Jellyfin" + DeleteRegKey HKLM "${REG_UNINST_KEY}" + +SectionEnd + + + +Function .onInit +; Setting up defaults + StrCpy $_INSTALLSERVICE_ "YES" + StrCpy $_SERVICESTART_ "YES" + StrCpy $_CUSTOMDATAFOLDER_ "NO" + StrCpy $_LOCALSYSTEMACCOUNT_ "NO" + StrCpy $_EXISTINGINSTALLATION_ "NO" + StrCpy $_EXISTINGSERVICE_ "NO" + + SetShellVarContext current + StrCpy $_JELLYFINDATADIR_ "$%ProgramData%\jellyfin\" + +;Detect if Jellyfin is already installed. +; In case it is installed, let the user choose either +; 1. Exit installer +; 2. Upgrade without messing with data +; 2a. Don't ask for any details, uninstall and install afresh with old settings + +; Read Registry for previous installation + ClearErrors + ReadRegStr "$0" HKLM "Software\Jellyfin" "InstallFolder" + IfErrors NoExisitingInstall + + DetailPrint "Existing Jellyfin detected at: $0" + StrCpy "$INSTDIR" "$0" ; set the location fro registry as new default + + StrCpy $_EXISTINGINSTALLATION_ "YES" ; Set our flag to be used later + SectionSetText ${InstallJellyfin} "Upgrade Jellyfin Server(required)" ; Change install text to "Upgrade" + +; check if there is a service called Jellyfin, there should be +; hack : nssm statuscode Jellyfin will return non zero return code in case it exists + ExecWait '"$INSTDIR\nssm.exe" statuscode Jellyfin' $0 + DetailPrint "Jellyfin service statuscode, $0" + IntCmp $0 0 NoService ; service doesn't exist, may be run from desktop shortcut + + ; if service was detected, set defaults going forward. + StrCpy $_EXISTINGSERVICE_ "YES" + StrCpy $_INSTALLSERVICE_ "YES" + StrCpy $_SERVICESTART_ "YES" + + ; check if service was run using Network Service account + ClearErrors + ReadRegStr "$_LOCALSYSTEMACCOUNT_" HKLM "Software\Jellyfin" "LocalSystemAccount" ; in case of error _LOCALSYSTEMACCOUNT_ will be NO as default + + ClearErrors + ReadRegStr $_JELLYFINDATADIR_ HKLM "Software\Jellyfin" "DataFolder" ; in case of error, the default holds + + ; Hide sections which will not be needed in case of previous install + SectionSetText ${InstallService} "" + SectionSetText ${StartService} "" + SectionSetText ${LocalSystemAccount} "" + SectionSetText ${CustomDataFolder} "" + SectionSetText ${Advanced} "" + + + NoService: ; existing install was present but no service was detected + +; Let the user know that we'll upgrade and provide an option to quit. + MessageBox MB_OKCANCEL|MB_ICONINFORMATION "Existing installation of Jellyfin was detected, it'll be upgraded, settings will be retained. \ + $\r$\nClick OK to proceed, Cancel to exit installer." /SD IDOK IDOK ProceedWithUpgrade + Quit ; Quit if the user is not sure about upgrade + + ProceedWithUpgrade: + + NoExisitingInstall: +; by this time, the variables have been correctly set to reflect previous install details + +FunctionEnd + +Function HideInstallDirectoryPage + ${If} $_EXISTINGINSTALLATION_ == "YES" ; Existing installation detected, so don't ask for InstallFolder + Abort + ${EndIf} +FunctionEnd + +; Don't show custom folder option in case it wasn't chosen +Function HideDataDirectoryPage + ${If} $_CUSTOMDATAFOLDER_ == "NO" + Abort + ${EndIf} +FunctionEnd + +; This function handles the choices during component selection +Function .onSelChange + SectionGetFlags ${CustomDataFolder} $0 + ${If} $0 = ${SF_SELECTED} + StrCpy $_CUSTOMDATAFOLDER_ "YES" + ${Else} + StrCpy $_CUSTOMDATAFOLDER_ "NO" + ${EndIf} + +; If we are not installing service, we don't need to set the NetworkService account or StartService + SectionGetFlags ${InstallService} $0 + ${If} $0 = ${SF_SELECTED} + StrCpy $_INSTALLSERVICE_ "YES" + SectionGetFlags ${LocalSystemAccount} $0 + IntOp $0 $0 | ${SF_RO} + IntOp $0 $0 ^ ${SF_RO} + SectionSetFlags ${LocalSystemAccount} $0 + SectionGetFlags ${StartService} $0 + IntOp $0 $0 | ${SF_RO} + IntOp $0 $0 ^ ${SF_RO} + SectionSetFlags ${StartService} $0 + ${Else} + StrCpy $_INSTALLSERVICE_ "NO" + IntOp $0 ${SF_USELECTED} | ${SF_RO} + SectionSetFlags ${LocalSystemAccount} $0 + SectionSetFlags ${StartService} $0 + ${EndIf} + + SectionGetFlags ${StartService} $0 + ${If} $0 = ${SF_SELECTED} + StrCpy $_SERVICESTART_ "YES" + ${Else} + StrCpy $_SERVICESTART_ "NO" + ${EndIf} + + SectionGetFlags ${LocalSystemAccount} $0 + ${If} $0 = ${SF_SELECTED} + StrCpy $_LOCALSYSTEMACCOUNT_ "YES" + ${Else} + StrCpy $_LOCALSYSTEMACCOUNT_ "NO" + ${EndIf} + + +FunctionEnd + +Function ConfirmationPage + !insertmacro MUI_HEADER_TEXT "Confirmation Page" "Please confirm your choices for Jellyfin Server installation" + + nsDialogs::Create 1018 + + ${NSD_CreateLabel} 0 0 100% 100% "The installer will proceed based on the following inputs gathered on earlier screens.$\r$\n$\r$\n\ + Installation Folder : $INSTDIR$\r$\n\ + Service install : $_INSTALLSERVICE_$\r$\n\ + Service start : $_SERVICESTART_$\r$\n\ + Local System account for service: $_LOCALSYSTEMACCOUNT_$\r$\n\ + Custom Metadata folder : $_CUSTOMDATAFOLDER_$\r$\n\ + Jellyfin Metadata Folder: $_JELLYFINDATADIR_" + nsDialogs::Show + +FunctionEnd + +Function .onInstSuccess + ExecShell "open" "http://localhost:8096" +FunctionEnd |
