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; } + } +}