diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index e5df873f5b..adeb997125 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -14,6 +14,7 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.SubtitleDtos;
+using Jellyfin.LibAssUtils;
using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
@@ -325,6 +326,74 @@ public class SubtitleController : BaseJellyfinApiController
startPositionTicks ?? routeStartPositionTicks);
}
+ ///
+ /// Gets a list of fonts for given ASS subtitles file.
+ ///
+ /// The (route) item id.
+ /// The (route) media source id.
+ /// The (route) subtitle stream index.
+ /// File returned.
+ /// A with the subtitle file.
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Fonts")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public IAsyncEnumerable GetSubtitleFonts(
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex)
+ {
+ return GetSubtitleFontsWithTicks(routeItemId, routeMediaSourceId, routeIndex, 0);
+ }
+
+ ///
+ /// Gets a list of fonts for given ASS subtitles file.
+ ///
+ /// The (route) item id.
+ /// The (route) media source id.
+ /// The (route) subtitle stream index.
+ /// The (route) start position of the subtitle in ticks.
+ /// File returned.
+ /// A with the subtitle file.
+ [HttpGet("Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Fonts")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ public async IAsyncEnumerable GetSubtitleFontsWithTicks(
+ [FromRoute, Required] Guid routeItemId,
+ [FromRoute, Required] string routeMediaSourceId,
+ [FromRoute, Required] int routeIndex,
+ [FromRoute, Required] long routeStartPositionTicks)
+ {
+ // return File(,MimeTypes.GetMimeType("file." + format));
+ var stream = await EncodeSubtitles(
+ routeItemId,
+ routeMediaSourceId,
+ routeIndex,
+ "ass", // TODO: problem: "ssa" subtitles will probably be converted instead of grabbed as-is, and that'll throw out Styles
+ routeStartPositionTicks,
+ null,
+ false).ConfigureAwait(false);
+
+ var fontInfos = LibAssUtils.Fonts.GetFontsForSubtitlesBuffer(stream);
+
+ foreach (var fontInfo in fontInfos)
+ {
+ if (fontInfo.path is null)
+ {
+ continue;
+ }
+
+ yield return new FontStreamInfo
+ {
+ Path = fontInfo.path,
+ Key = string.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", fontInfo.font_name, fontInfo.bold, fontInfo.italic),
+ FontName = fontInfo.font_name,
+ PostScriptName = fontInfo.postscript_name,
+ Bold = fontInfo.bold,
+ Italic = fontInfo.italic,
+ };
+ }
+ }
+
///
/// Gets an HLS subtitle playlist.
///
@@ -577,4 +646,55 @@ public class SubtitleController : BaseJellyfinApiController
// returning HTTP 204 will break the SubtitlesOctopus
return Ok();
}
+
+ ///
+ /// Gets a font file from installed server-side fonts.
+ ///
+ /// The name of the font file family, full name or PostScript name.
+ /// Font's boldness score.
+ /// Whether the font is supposed to be italic or not.
+ /// Font file retrieved.
+ /// The font file matched to the query.
+ [HttpGet("Fonts/{family},{bold},{italic}")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesFile("font/*")]
+ public ActionResult GetStreamFont(
+ [FromRoute, Required] string family,
+ [FromRoute, Required] int bold,
+ [FromRoute, Required] int italic)
+ {
+ LibAssUtils.FontDesc desc;
+ desc.Family = family;
+ desc.Bold = bold;
+ desc.Italic = italic;
+ var fontInfo = LibAssUtils.Fonts.GetFontForQuery(desc);
+
+ if (fontInfo.path is not null && fontInfo.postscript_name is not null)
+ {
+ Response.Headers["X-Selected-Font-Path"] = fontInfo.path;
+ Response.Headers["X-Selected-Font-Postscript-Name"] = fontInfo.postscript_name;
+ return PhysicalFile(fontInfo.path, MimeTypes.GetMimeType(fontInfo.path));
+ }
+
+ _logger.LogWarning("Font for such parameters was not found: {0},{1},{2}", family, bold, italic);
+
+ // returning HTTP 204 will break the SubtitlesOctopus
+ return Ok();
+ }
+
+ ///
+ /// Gets a font file from installed server-side fonts.
+ ///
+ /// The name of the font file family, full name or PostScript name.
+ /// Fallback font file retrieved.
+ /// The fallback font file.
+ [HttpGet("Fonts/{family}")]
+ [Authorize]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesFile("font/*")]
+ public ActionResult GetStreamFont([FromRoute, Required] string family)
+ {
+ return GetStreamFont(family, 0, 0);
+ }
}
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index 25feaa2d75..70c10d80c7 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -45,4 +45,10 @@
+
+
+ ..\..\..\..\Downloads\libass\Jellyfin.LibAssUtils\bin\Debug\net8.0\Jellyfin.LibAssUtils.dll
+
+
+
diff --git a/MediaBrowser.Model/Subtitles/FontStreamInfo.cs b/MediaBrowser.Model/Subtitles/FontStreamInfo.cs
new file mode 100644
index 0000000000..18cb02c6ca
--- /dev/null
+++ b/MediaBrowser.Model/Subtitles/FontStreamInfo.cs
@@ -0,0 +1,52 @@
+using System;
+
+namespace MediaBrowser.Model.Subtitles
+{
+ ///
+ /// Class FontStreamInfo.
+ ///
+ public class FontStreamInfo
+ {
+ ///
+ /// Gets or sets the filesystem path of the font file.
+ ///
+ /// The filesystem path.
+ public required string Path { get; set; }
+
+ ///
+ /// Gets or sets the lookup key that can be used to grab the underlying font file from Jellyfin API.
+ ///
+ /// The lookup key.
+ public required string Key { get; set; }
+
+ ///
+ /// Gets or sets the font name as specified in the subtitle file that referenced it.
+ /// This might be either family name, full name or PostScript name.
+ /// It does not uniquely identify the font on its own.
+ /// Any given font file may match multiple names.
+ ///
+ /// The name.
+ public string? FontName { get; set; }
+
+ ///
+ /// Gets or sets the PostScript name of the font.
+ /// The PostScript font name is supposed to be unique, locale-independent
+ /// and abide by certain restrictions to allowed format.
+ /// Unfortunately, not all font files out there physically abide by these standards.
+ ///
+ /// The PostScript name.
+ public string? PostScriptName { get; set; }
+
+ ///
+ /// Gets or sets the Bold score.
+ ///
+ /// The Bold.
+ public int Bold { get; set; }
+
+ ///
+ /// Gets or sets the Italic score.
+ ///
+ /// The Italic.
+ public int Italic { get; set; }
+ }
+}