Browse Source

fix external ssa subtitles from media servers (#2450)

pull/2451/head
Jason Dove 3 months ago committed by GitHub
parent
commit
b790b5944c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 3
      ErsatzTV.Application/Streaming/Queries/GetSeekTextSubtitleProcess.cs
  3. 3
      ErsatzTV.Application/Streaming/Queries/GetSeekTextSubtitleProcessHandler.cs
  4. 2
      ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathById.cs
  5. 18
      ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs
  6. 3
      ErsatzTV.Application/Subtitles/SubtitlePathAndCodec.cs
  7. 6
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  8. 2
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  9. 2
      ErsatzTV.FFmpeg/Pipeline/IPipelineBuilder.cs
  10. 4
      ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs
  11. 17
      ErsatzTV/Controllers/InternalController.cs

1
CHANGELOG.md

@ -65,6 +65,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -65,6 +65,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix fallback filler duration on mirror channels
- Fix slow startup caused by check for overlapping playout items
- Fix green line in *most* cases when overlaying content using NVIDIA acceleration and H264 output
- Fix non-SRT (e.g. SSA/ASS) external subtitle playback from media servers
### Changed
- Filler presets: use separate text fields for `hours`, `minutes` and `seconds` duration

3
ErsatzTV.Application/Streaming/Queries/GetSeekTextSubtitleProcess.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using ErsatzTV.Application.Subtitles;
using ErsatzTV.Core;
namespace ErsatzTV.Application.Streaming;
public record GetSeekTextSubtitleProcess(string SubtitlePath, TimeSpan Seek)
public record GetSeekTextSubtitleProcess(SubtitlePathAndCodec PathAndCodec, TimeSpan Seek)
: IRequest<Either<BaseError, SeekTextSubtitleProcess>>;

3
ErsatzTV.Application/Streaming/Queries/GetSeekTextSubtitleProcessHandler.cs

@ -31,7 +31,8 @@ public class GetSeekTextSubtitleProcessHandler( @@ -31,7 +31,8 @@ public class GetSeekTextSubtitleProcessHandler(
{
Command process = await ffmpegProcessService.SeekTextSubtitle(
ffmpegPath,
request.SubtitlePath,
request.PathAndCodec.Path,
request.PathAndCodec.Codec,
request.Seek);
return new SeekTextSubtitleProcess(process);

2
ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathById.cs

@ -2,4 +2,4 @@ using ErsatzTV.Core; @@ -2,4 +2,4 @@ using ErsatzTV.Core;
namespace ErsatzTV.Application.Subtitles.Queries;
public record GetSubtitlePathById(int Id) : IRequest<Either<BaseError, string>>;
public record GetSubtitlePathById(int Id) : IRequest<Either<BaseError, SubtitlePathAndCodec>>;

18
ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs

@ -9,9 +9,9 @@ using Microsoft.EntityFrameworkCore; @@ -9,9 +9,9 @@ using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Subtitles.Queries;
public class GetSubtitlePathByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
: IRequestHandler<GetSubtitlePathById, Either<BaseError, string>>
: IRequestHandler<GetSubtitlePathById, Either<BaseError, SubtitlePathAndCodec>>
{
public async Task<Either<BaseError, string>> Handle(
public async Task<Either<BaseError, SubtitlePathAndCodec>> Handle(
GetSubtitlePathById request,
CancellationToken cancellationToken)
{
@ -22,33 +22,35 @@ public class GetSubtitlePathByIdHandler(IDbContextFactory<TvContext> dbContextFa @@ -22,33 +22,35 @@ public class GetSubtitlePathByIdHandler(IDbContextFactory<TvContext> dbContextFa
foreach (var subtitle in maybeSubtitle)
{
string path = subtitle.Path;
if (subtitle is { SubtitleKind: SubtitleKind.Embedded, IsExtracted: true })
{
return Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path);
path = Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path);
}
foreach (string plexUrl in await GetPlexUrl(request.Id, dbContext, maybeSubtitle))
{
return plexUrl;
path = plexUrl;
}
foreach (string jellyfinUrl in await GetJellyfinUrl(request.Id, dbContext, maybeSubtitle))
{
return jellyfinUrl;
path = jellyfinUrl;
}
foreach (string embyUrl in await GetEmbyUrl(request.Id, dbContext, maybeSubtitle))
{
return embyUrl;
path = embyUrl;
}
return subtitle.Path;
return new SubtitlePathAndCodec(path, subtitle.Codec);
}
return BaseError.New($"Unable to locate subtitle with id {request.Id}");
}
protected static async Task<Option<string>> GetPlexUrl(
protected static async Task<Option<string>> GetPlexUrl(
int subtitleId,
TvContext dbContext,
Option<Subtitle> maybeSubtitle)

3
ErsatzTV.Application/Subtitles/SubtitlePathAndCodec.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.Subtitles;
public record SubtitlePathAndCodec(string Path, string Codec);

6
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -967,7 +967,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -967,7 +967,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
watermarkWidthPercent,
cancellationToken);
public async Task<Command> SeekTextSubtitle(string ffmpegPath, string inputFile, TimeSpan seek)
public async Task<Command> SeekTextSubtitle(string ffmpegPath, string inputFile, string codec, TimeSpan seek)
{
var videoInputFile = new VideoInputFile(
inputFile,
@ -975,7 +975,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -975,7 +975,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
{
new(
0,
string.Empty,
codec,
string.Empty,
None,
ColorParams.Default,
@ -1002,7 +1002,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -1002,7 +1002,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
FileSystemLayout.FontsCacheFolder,
ffmpegPath);
FFmpegPipeline pipeline = pipelineBuilder.Seek(inputFile, seek);
FFmpegPipeline pipeline = pipelineBuilder.Seek(inputFile, codec, seek);
return GetCommand(ffmpegPath, videoInputFile, None, None, None, None, pipeline, false);
}

2
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -90,5 +90,5 @@ public interface IFFmpegProcessService @@ -90,5 +90,5 @@ public interface IFFmpegProcessService
int watermarkWidthPercent,
CancellationToken cancellationToken);
Task<Command> SeekTextSubtitle(string ffmpegPath, string inputFile, TimeSpan seek);
Task<Command> SeekTextSubtitle(string ffmpegPath, string inputFile, string codec, TimeSpan seek);
}

2
ErsatzTV.FFmpeg/Pipeline/IPipelineBuilder.cs

@ -3,7 +3,7 @@ namespace ErsatzTV.FFmpeg.Pipeline; @@ -3,7 +3,7 @@ namespace ErsatzTV.FFmpeg.Pipeline;
public interface IPipelineBuilder
{
FFmpegPipeline Resize(string outputFile, FrameSize scaledSize);
FFmpegPipeline Seek(string inputFile, TimeSpan seek);
FFmpegPipeline Seek(string inputFile, string codec, TimeSpan seek);
FFmpegPipeline Concat(ConcatInputFile concatInputFile, FFmpegState ffmpegState);
FFmpegPipeline WrapSegmenter(ConcatInputFile concatInputFile, FFmpegState ffmpegState);
FFmpegPipeline Build(FFmpegState ffmpegState, FrameState desiredState);

4
ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs

@ -75,12 +75,14 @@ public abstract class PipelineBuilderBase : IPipelineBuilder @@ -75,12 +75,14 @@ public abstract class PipelineBuilderBase : IPipelineBuilder
return new FFmpegPipeline(pipelineSteps, false);
}
public FFmpegPipeline Seek(string inputFile, TimeSpan seek)
public FFmpegPipeline Seek(string inputFile, string codec, TimeSpan seek)
{
IPipelineStep outputFormat = Path.GetExtension(inputFile).ToLowerInvariant() switch
{
".ass" or ".ssa" => new OutputFormatAss(),
".vtt" => new OutputFormatWebVtt(),
_ when codec.ToLowerInvariant() is "ass" or "ssa" => new OutputFormatAss(),
_ when codec.ToLowerInvariant() is "vtt" => new OutputFormatWebVtt(),
_ => new OutputFormatSrt()
};

17
ErsatzTV/Controllers/InternalController.cs

@ -7,6 +7,7 @@ using ErsatzTV.Application.Jellyfin; @@ -7,6 +7,7 @@ using ErsatzTV.Application.Jellyfin;
using ErsatzTV.Application.MediaItems;
using ErsatzTV.Application.Plex;
using ErsatzTV.Application.Streaming;
using ErsatzTV.Application.Subtitles;
using ErsatzTV.Application.Subtitles.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.FFmpeg;
@ -202,21 +203,23 @@ public class InternalController : ControllerBase @@ -202,21 +203,23 @@ public class InternalController : ControllerBase
[HttpGet("/media/subtitle/{id:int}")]
public async Task<IActionResult> GetSubtitle(int id, [FromQuery] long? seekToMs)
{
Either<BaseError, string> maybePath = await _mediator.Send(new GetSubtitlePathById(id));
Either<BaseError, SubtitlePathAndCodec> maybePath = await _mediator.Send(new GetSubtitlePathById(id));
foreach (string path in maybePath.RightToSeq())
foreach (SubtitlePathAndCodec pathAndCodec in maybePath.RightToSeq())
{
string mimeType = Path.GetExtension(path).ToLowerInvariant() switch
string mimeType = Path.GetExtension(pathAndCodec.Path ?? string.Empty).ToLowerInvariant() switch
{
".ass" or ".ssa" => "text/x-ssa",
".vtt" => "text/vtt",
_ when pathAndCodec.Codec.ToLowerInvariant() is "ass" or "ssa" => "text/x-ssa",
_ when pathAndCodec.Codec.ToLowerInvariant() is "vtt" => "text/vtt",
_ => "application/x-subrip"
};
if (seekToMs is > 0)
{
Either<BaseError, SeekTextSubtitleProcess> maybeProcess = await _mediator.Send(
new GetSeekTextSubtitleProcess(path, TimeSpan.FromMilliseconds(seekToMs.Value)));
new GetSeekTextSubtitleProcess(pathAndCodec, TimeSpan.FromMilliseconds(seekToMs.Value)));
foreach (SeekTextSubtitleProcess processModel in maybeProcess.RightToSeq())
{
Command command = processModel.Process;
@ -250,12 +253,12 @@ public class InternalController : ControllerBase @@ -250,12 +253,12 @@ public class InternalController : ControllerBase
return new NotFoundResult();
}
if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
if (pathAndCodec.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
return new RedirectResult(path);
return new RedirectResult(pathAndCodec.Path);
}
return new PhysicalFileResult(path, mimeType);
return new PhysicalFileResult(pathAndCodec.Path, mimeType);
}
return new NotFoundResult();

Loading…
Cancel
Save