using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Repositories; using LanguageExt; using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Streaming.Queries { public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler { private readonly FFmpegProcessService _ffmpegProcessService; private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlayoutRepository _playoutRepository; public GetPlayoutItemProcessByChannelNumberHandler( IChannelRepository channelRepository, IConfigElementRepository configElementRepository, IPlayoutRepository playoutRepository, IMediaSourceRepository mediaSourceRepository, FFmpegProcessService ffmpegProcessService, ILogger logger) : base(channelRepository, configElementRepository) { _playoutRepository = playoutRepository; _mediaSourceRepository = mediaSourceRepository; _ffmpegProcessService = ffmpegProcessService; _logger = logger; } protected override async Task> GetProcess( GetPlayoutItemProcessByChannelNumber _, Channel channel, string ffmpegPath) { DateTimeOffset now = DateTimeOffset.Now; Option maybePlayoutItem = await _playoutRepository.GetPlayoutItem(channel.Id, now); return await maybePlayoutItem.Match>>( async playoutItem => { MediaVersion version = playoutItem.MediaItem switch { Movie m => m.MediaVersions.Head(), Episode e => e.MediaVersions.Head(), _ => throw new ArgumentOutOfRangeException(nameof(playoutItem)) }; MediaFile file = version.MediaFiles.Head(); string path = file.Path; if (playoutItem.MediaItem is PlexMovie plexMovie) { path = await GetReplacementPlexPath(plexMovie.LibraryPathId, path); } return _ffmpegProcessService.ForPlayoutItem( ffmpegPath, channel, version, path, playoutItem.StartOffset, now); }, async () => { if (channel.FFmpegProfile.Transcode) { Option maybeDuration = await _playoutRepository.GetNextItemStart(channel.Id, now) .MapT(nextStart => nextStart - now); return _ffmpegProcessService.ForOfflineImage(ffmpegPath, channel, maybeDuration); } var message = $"Unable to locate playout item for channel {channel.Number}; offline image is unavailable because transcoding is disabled in ffmpeg profile '{channel.FFmpegProfile.Name}'"; return BaseError.New(message); }); } private async Task GetReplacementPlexPath(int libraryPathId, string path) { List replacements = await _mediaSourceRepository.GetPlexPathReplacementsByLibraryId(libraryPathId); // TODO: this might barf mixing platforms (i.e. plex on linux, etv on windows) Option maybeReplacement = replacements .SingleOrDefault(r => path.StartsWith(r.PlexPath + Path.DirectorySeparatorChar)); return maybeReplacement.Match( replacement => { string finalPath = path.Replace(replacement.PlexPath, replacement.LocalPath); _logger.LogInformation( "Replacing plex path {PlexPath} with {LocalPath} resulting in {FinalPath}", replacement.PlexPath, replacement.LocalPath, finalPath); return finalPath; }, () => path); } } }