aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Helpers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Helpers')
-rw-r--r--Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs125
-rw-r--r--Jellyfin.Api/Helpers/HlsHelpers.cs95
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs6
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs6
4 files changed, 229 insertions, 3 deletions
diff --git a/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
new file mode 100644
index 000000000..95f1906ef
--- /dev/null
+++ b/Jellyfin.Api/Helpers/HlsCodecStringHelpers.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Globalization;
+using System.Text;
+
+namespace Jellyfin.Api.Helpers
+{
+ /// <summary>
+ /// Hls Codec string helpers.
+ /// </summary>
+ public static class HlsCodecStringHelpers
+ {
+ /// <summary>
+ /// Gets a MP3 codec string.
+ /// </summary>
+ /// <returns>MP3 codec string.</returns>
+ public static string GetMP3String()
+ {
+ return "mp4a.40.34";
+ }
+
+ /// <summary>
+ /// Gets an AAC codec string.
+ /// </summary>
+ /// <param name="profile">AAC profile.</param>
+ /// <returns>AAC codec string.</returns>
+ public static string GetAACString(string profile)
+ {
+ StringBuilder result = new StringBuilder("mp4a", 9);
+
+ if (string.Equals(profile, "HE", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".40.5");
+ }
+ else
+ {
+ // Default to LC if profile is invalid
+ result.Append(".40.2");
+ }
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets a H.264 codec string.
+ /// </summary>
+ /// <param name="profile">H.264 profile.</param>
+ /// <param name="level">H.264 level.</param>
+ /// <returns>H.264 string.</returns>
+ public static string GetH264String(string profile, int level)
+ {
+ StringBuilder result = new StringBuilder("avc1", 11);
+
+ if (string.Equals(profile, "high", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".6400");
+ }
+ else if (string.Equals(profile, "main", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".4D40");
+ }
+ else if (string.Equals(profile, "baseline", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".42E0");
+ }
+ else
+ {
+ // Default to constrained baseline if profile is invalid
+ result.Append(".4240");
+ }
+
+ string levelHex = level.ToString("X2", CultureInfo.InvariantCulture);
+ result.Append(levelHex);
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets a H.265 codec string.
+ /// </summary>
+ /// <param name="profile">H.265 profile.</param>
+ /// <param name="level">H.265 level.</param>
+ /// <returns>H.265 string.</returns>
+ public static string GetH265String(string profile, int level)
+ {
+ // The h265 syntax is a bit of a mystery at the time this comment was written.
+ // This is what I've found through various sources:
+ // FORMAT: [codecTag].[profile].[constraint?].L[level * 30].[UNKNOWN]
+ StringBuilder result = new StringBuilder("hev1", 16);
+
+ if (string.Equals(profile, "main10", StringComparison.OrdinalIgnoreCase))
+ {
+ result.Append(".2.6");
+ }
+ else
+ {
+ // Default to main if profile is invalid
+ result.Append(".1.6");
+ }
+
+ result.Append(".L")
+ .Append(level * 3)
+ .Append(".B0");
+
+ return result.ToString();
+ }
+
+ /// <summary>
+ /// Gets an AC-3 codec string.
+ /// </summary>
+ /// <returns>AC-3 codec string.</returns>
+ public static string GetAC3String()
+ {
+ return "mp4a.a5";
+ }
+
+ /// <summary>
+ /// Gets an E-AC-3 codec string.
+ /// </summary>
+ /// <returns>E-AC-3 codec string.</returns>
+ public static string GetEAC3String()
+ {
+ return "mp4a.a6";
+ }
+ }
+}
diff --git a/Jellyfin.Api/Helpers/HlsHelpers.cs b/Jellyfin.Api/Helpers/HlsHelpers.cs
new file mode 100644
index 000000000..242496697
--- /dev/null
+++ b/Jellyfin.Api/Helpers/HlsHelpers.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.IO;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Api.Helpers
+{
+ /// <summary>
+ /// The hls helpers.
+ /// </summary>
+ public static class HlsHelpers
+ {
+ /// <summary>
+ /// Waits for a minimum number of segments to be available.
+ /// </summary>
+ /// <param name="playlist">The playlist string.</param>
+ /// <param name="segmentCount">The segment count.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <returns>A <see cref="Task"/> indicating the waiting process.</returns>
+ public static async Task WaitForMinimumSegmentCount(string playlist, int? segmentCount, ILogger logger, CancellationToken cancellationToken)
+ {
+ logger.LogDebug("Waiting for {0} segments in {1}", segmentCount, playlist);
+
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
+ var fileStream = new FileStream(
+ playlist,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ IODefaults.FileStreamBufferSize,
+ FileOptions.SequentialScan);
+ await using (fileStream.ConfigureAwait(false))
+ {
+ using var reader = new StreamReader(fileStream);
+ var count = 0;
+
+ while (!reader.EndOfStream)
+ {
+ var line = await reader.ReadLineAsync().ConfigureAwait(false);
+
+ if (line.IndexOf("#EXTINF:", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ count++;
+ if (count >= segmentCount)
+ {
+ logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
+ return;
+ }
+ }
+ }
+ }
+
+ await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ }
+ catch (IOException)
+ {
+ // May get an error if the file is locked
+ }
+
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ /// <summary>
+ /// Gets the hls playlist text.
+ /// </summary>
+ /// <param name="path">The path to the playlist file.</param>
+ /// <param name="segmentLength">The segment length.</param>
+ /// <returns>The playlist text as a string.</returns>
+ public static 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();
+
+ text = text.Replace("#EXTM3U", "#EXTM3U\n#EXT-X-PLAYLIST-TYPE:EVENT", StringComparison.InvariantCulture);
+
+ var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(CultureInfo.InvariantCulture);
+
+ 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;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 299c7d4aa..d9e993d49 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
@@ -172,5 +173,10 @@ namespace Jellyfin.Api.Helpers
.Select(i => i!.Value)
.ToArray();
}
+
+ internal static IPAddress NormalizeIp(IPAddress ip)
+ {
+ return ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4() : ip;
+ }
}
}
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index fc38eacaf..a5092ac1d 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -219,7 +219,7 @@ namespace Jellyfin.Api.Helpers
/// <param name="playSessionId">The play session identifier.</param>
/// <param name="deleteFiles">The delete files.</param>
/// <returns>Task.</returns>
- public Task KillTranscodingJobs(string deviceId, string playSessionId, Func<string, bool> deleteFiles)
+ public Task KillTranscodingJobs(string deviceId, string? playSessionId, Func<string, bool> deleteFiles)
{
return KillTranscodingJobs(
j => string.IsNullOrWhiteSpace(playSessionId)
@@ -503,9 +503,9 @@ namespace Jellyfin.Api.Helpers
}
}
- var process = new Process()
+ var process = new Process
{
- StartInfo = new ProcessStartInfo()
+ StartInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,