using Dapper; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Jellyfin; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Subtitles.Queries; public class GetSubtitlePathByIdHandler : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory; public GetSubtitlePathByIdHandler(IDbContextFactory dbContextFactory) => _dbContextFactory = dbContextFactory; public async Task> Handle( GetSubtitlePathById request, CancellationToken cancellationToken) { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Option maybeSubtitle = await dbContext.Subtitles .SelectOneAsync(s => s.Id, s => s.Id == request.Id); foreach (string plexUrl in await GetPlexUrl(request, dbContext, maybeSubtitle)) { return plexUrl; } foreach (string jellyfinUrl in await GetJellyfinUrl(request, dbContext, maybeSubtitle)) { return jellyfinUrl; } foreach (string embyUrl in await GetEmbyUrl(request, dbContext, maybeSubtitle)) { return embyUrl; } return maybeSubtitle .Map(s => s.Path) .ToEither(BaseError.New($"Unable to locate subtitle with id {request.Id}")); } private static async Task> GetPlexUrl( GetSubtitlePathById request, TvContext dbContext, Option maybeSubtitle) { // check for plex episode Option maybePlexId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select PMS.Id from PlexMediaSource PMS inner join Library L on PMS.Id = L.MediaSourceId inner join LibraryPath LP on L.Id = LP.LibraryId inner join MediaItem MI on LP.Id = MI.LibraryPathId inner join EpisodeMetadata EM on EM.EpisodeId = MI.Id inner join Subtitle S on EM.Id = S.EpisodeMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); // check for plex movie if (maybePlexId.IsNone) { maybePlexId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select PMS.Id from PlexMediaSource PMS inner join Library L on PMS.Id = L.MediaSourceId inner join LibraryPath LP on L.Id = LP.LibraryId inner join MediaItem MI on LP.Id = MI.LibraryPathId inner join MovieMetadata MM on MM.MovieId = MI.Id inner join Subtitle S on MM.Id = S.MovieMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); } foreach (int plexMediaSourceId in maybePlexId) { foreach (string subtitlePath in maybeSubtitle.Map(s => s.Path)) { return $"http://localhost:{Settings.StreamingPort}/media/plex/{plexMediaSourceId}/{subtitlePath}"; } } return Option.None; } private static async Task> GetJellyfinUrl( GetSubtitlePathById request, TvContext dbContext, Option maybeSubtitle) { // check for jellyfin episode Option maybeJellyfinId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select JE.ItemId from JellyfinEpisode JE inner join EpisodeMetadata EM on EM.EpisodeId = JE.Id inner join Subtitle S on EM.Id = S.EpisodeMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); // check for jellyfin movie if (maybeJellyfinId.IsNone) { maybeJellyfinId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select JM.ItemId from JellyfinMovie JM inner join MovieMetadata MM on MM.MovieId = JM.Id inner join Subtitle S on MM.Id = S.MovieMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); } foreach (string jellyfinItemId in maybeJellyfinId) { foreach (Subtitle subtitle in maybeSubtitle) { int index = subtitle.StreamIndex - JellyfinStream.ExternalStreamOffset; string extension = Subtitle.ExtensionForCodec(subtitle.Codec); var subtitlePath = $"Videos/{jellyfinItemId}/{jellyfinItemId}/Subtitles/{index}/{index}/Stream.{extension}"; return $"http://localhost:{Settings.StreamingPort}/media/jellyfin/{subtitlePath}"; } } return Option.None; } private static async Task> GetEmbyUrl( GetSubtitlePathById request, TvContext dbContext, Option maybeSubtitle) { // check for emby episode Option maybeEmbyId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select EE.ItemId from EmbyEpisode EE inner join EpisodeMetadata EM on EM.EpisodeId = EE.Id inner join Subtitle S on EM.Id = S.EpisodeMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); // check for emby movie if (maybeEmbyId.IsNone) { maybeEmbyId = await dbContext.Connection.QuerySingleOrDefaultAsync( @"select EM.ItemId from EmbyMovie EM inner join MovieMetadata MM on MM.MovieId = EM.Id inner join Subtitle S on MM.Id = S.MovieMetadataId where S.Id = @SubtitleId", new { SubtitleId = request.Id }) .Map(Optional); } foreach (string embyItemId in maybeEmbyId) { foreach (Subtitle subtitle in maybeSubtitle) { string extension = Subtitle.ExtensionForCodec(subtitle.Codec); var subtitlePath = $"Videos/{embyItemId}/{subtitle.Path}/Subtitles/{subtitle.StreamIndex}/Stream.{extension}"; return $"http://localhost:{Settings.StreamingPort}/media/emby/{subtitlePath}"; } } return Option.None; } }