Browse Source

proxy external subtitle files (#1203)

pull/1206/head
Jason Dove 3 years ago committed by GitHub
parent
commit
35445e2b3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathById.cs
  3. 27
      ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs
  4. 18
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  5. 3
      ErsatzTV.FFmpeg/Filter/SubtitlesFilter.cs
  6. 1
      ErsatzTV.Infrastructure/Data/TvContext.cs
  7. 20
      ErsatzTV/Controllers/InternalController.cs

1
CHANGELOG.md

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Note that these subtitles are not currently supported in ETV, but they did cause a playback issue
- Fix Jellyfin, Emby and Plex library scans that wouldn't work in certain timezones
- Fix song normalization to match FFmpeg Profile bit depth
- Fix bug playing some external subtitle files (e.g. with an apostrophe in the file name)
### Changed
- Ignore case of video and audio file extensions in local folder scanner

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

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

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

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
using ErsatzTV.Core;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
namespace ErsatzTV.Application.Subtitles.Queries;
public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, Either<BaseError, string>>
{
private readonly IDbContextFactory<TvContext> _dbContextFactory;
public GetSubtitlePathByIdHandler(IDbContextFactory<TvContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
public async Task<Either<BaseError, string>> Handle(
GetSubtitlePathById request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
Option<string> maybeSubtitlePath = await dbContext.Subtitles
.SelectOneAsync(s => s.Id, s => s.Id == request.Id)
.MapT(s => s.Path);
return maybeSubtitlePath.ToEither(BaseError.New($"Unable to locate subtitle with id {request.Id}"));
}
}

18
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -98,6 +98,15 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -98,6 +98,15 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
preferredSubtitleLanguage,
subtitleMode);
foreach (Subtitle subtitle in maybeSubtitle)
{
if (subtitle.SubtitleKind == SubtitleKind.Sidecar)
{
// proxy to avoid dealing with escaping
subtitle.Path = $"http://localhost:{Settings.ListenPort}/media/subtitle/{subtitle.Id}";
}
}
Option<WatermarkOptions> watermarkOptions = disableWatermarks
? None
: await _ffmpegProcessService.GetWatermarkOptions(
@ -194,9 +203,12 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -194,9 +203,12 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
subtitle.Codec,
StreamKind.Video);
string path = subtitle.IsImage
? videoPath
: Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path);
string path = subtitle.IsImage switch
{
true => videoPath,
false when subtitle.SubtitleKind == SubtitleKind.Sidecar => subtitle.Path,
_ => Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path)
};
return new SubtitleInputFile(
path,

3
ErsatzTV.FFmpeg/Filter/SubtitlesFilter.cs

@ -34,7 +34,8 @@ public class SubtitlesFilter : BaseFilter @@ -34,7 +34,8 @@ public class SubtitlesFilter : BaseFilter
// escape brackets after escaping for windows
effectiveFile = effectiveFile
.Replace(@"[", @"\[")
.Replace(@"]", @"\]");
.Replace(@"]", @"\]")
.Replace(@"http://localhost:", @"http\\://localhost\\:");
return $"subtitles={effectiveFile}:fontsdir={fontsDir}";
}

1
ErsatzTV.Infrastructure/Data/TvContext.cs

@ -84,6 +84,7 @@ public class TvContext : DbContext @@ -84,6 +84,7 @@ public class TvContext : DbContext
public DbSet<LanguageCode> LanguageCodes { get; set; }
public DbSet<TraktList> TraktLists { get; set; }
public DbSet<FillerPreset> FillerPresets { get; set; }
public DbSet<Subtitle> Subtitles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseLoggerFactory(_loggerFactory);

20
ErsatzTV/Controllers/InternalController.cs

@ -4,6 +4,7 @@ using ErsatzTV.Application.Emby; @@ -4,6 +4,7 @@ using ErsatzTV.Application.Emby;
using ErsatzTV.Application.Jellyfin;
using ErsatzTV.Application.Plex;
using ErsatzTV.Application.Streaming;
using ErsatzTV.Application.Subtitles.Queries;
using ErsatzTV.Core;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Extensions;
@ -143,4 +144,23 @@ public class InternalController : ControllerBase @@ -143,4 +144,23 @@ public class InternalController : ControllerBase
return new RedirectResult(fullPath.ToString());
});
}
[HttpGet("/media/subtitle/{id:int}")]
public async Task<IActionResult> GetSubtitle(int id)
{
Either<BaseError, string> path = await _mediator.Send(new GetSubtitlePathById(id));
return path.Match<IActionResult>(
Left: _ => new NotFoundResult(),
Right: r =>
{
string mimeType = Path.GetExtension(r).ToLowerInvariant() switch
{
"ass" or "ssa" => "text/x-ssa",
"vtt" => "text/vtt",
_ => "application/x-subrip",
};
return new PhysicalFileResult(r, mimeType);
});
}
}

Loading…
Cancel
Save