Beispiel #1
0
    public async Task <Option <Collection> > GetCollectionWithCollectionItemsUntracked(int id)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

        return(await dbContext.Collections
               .Include(c => c.CollectionItems)
               .OrderBy(c => c.Id)
               .SingleOrDefaultAsync(c => c.Id == id)
               .Map(Optional));
    }
    public async Task <Either <BaseError, Unit> > Handle(CreateFillerPreset request, CancellationToken cancellationToken)
    {
        try
        {
            await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

            var fillerPreset = new FillerPreset
            {
                Name               = request.Name,
                FillerKind         = request.FillerKind,
                FillerMode         = request.FillerMode,
                Duration           = request.Duration,
                Count              = request.Count,
                PadToNearestMinute = request.PadToNearestMinute,
                CollectionType     = request.CollectionType,
                CollectionId       = request.CollectionId,
                MediaItemId        = request.MediaItemId,
                MultiCollectionId  = request.MultiCollectionId,
                SmartCollectionId  = request.SmartCollectionId
            };

            await dbContext.FillerPresets.AddAsync(fillerPreset, cancellationToken);

            await dbContext.SaveChangesAsync(cancellationToken);

            return(Unit.Default);
        }
        catch (Exception ex)
        {
            _client.Notify(ex);
            return(BaseError.New(ex.Message));
        }
    }
    public async Task <Either <BaseError, Unit> > Handle(BuildPlayout request, CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, Playout> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, playout => ApplyUpdateRequest(dbContext, request, playout)));
    }
    public async Task <Either <BaseError, Unit> > Handle(
        AddSeasonToCollection request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, Parameters> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, parameters => ApplyAddSeasonRequest(dbContext, parameters)));
    }
    public async Task <Either <BaseError, Unit> > Handle(
        MoveLocalLibraryPath request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, Parameters> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, parameters => MovePath(dbContext, parameters)));
    }
Beispiel #6
0
    public async Task <Either <BaseError, IEnumerable <ProgramScheduleItemViewModel> > > Handle(
        ReplaceProgramScheduleItems request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, ProgramSchedule> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, ps => PersistItems(dbContext, request, ps)));
    }
Beispiel #7
0
    public async Task <Either <BaseError, UpdateProgramScheduleResult> > Handle(
        UpdateProgramSchedule request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, ProgramSchedule> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, ps => ApplyUpdateRequest(dbContext, ps, request)));
    }
Beispiel #8
0
    public async Task <Either <BaseError, Unit> > Handle(
        UpdateCollectionCustomOrder request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, Collection> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, c => ApplyUpdateRequest(dbContext, c, request)));
    }
    public async Task <Either <BaseError, Unit> > Handle(
        DeleteLocalLibrary request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, LocalLibrary> validation = await LocalLibraryMustExist(dbContext, request);

        return(await LanguageExtensions.Apply(validation, localLibrary => DoDeletion(dbContext, localLibrary)));
    }
    public async Task <Either <BaseError, LocalLibraryViewModel> > Handle(
        CreateLocalLibrary request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, LocalLibrary> validation = await Validate(dbContext, request);

        return(await LanguageExtensions.Apply(validation, localLibrary => PersistLocalLibrary(dbContext, localLibrary)));
    }
Beispiel #11
0
    public async Task <Either <BaseError, Unit> > Handle(
        ExtractEmbeddedSubtitles request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Validation <BaseError, string> validation = await FFmpegPathMustExist(dbContext);

        return(await validation.Match(
                   ffmpegPath => ExtractAll(dbContext, request, ffmpegPath, cancellationToken),
                   error => Task.FromResult <Either <BaseError, Unit> >(error.Join())));
    }
    public async Task <HealthCheckResult> Check(CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        IQueryable <MediaItem> mediaItems = dbContext.MediaItems
                                            .Filter(mi => mi.State == MediaItemState.Unavailable)
                                            .Include(mi => (mi as Episode).MediaVersions)
                                            .ThenInclude(mv => mv.MediaFiles)
                                            .Include(mi => (mi as Movie).MediaVersions)
                                            .ThenInclude(mv => mv.MediaFiles)
                                            .Include(mi => (mi as MusicVideo).MediaVersions)
                                            .ThenInclude(mv => mv.MediaFiles)
                                            .Include(mi => (mi as OtherVideo).MediaVersions)
                                            .ThenInclude(mv => mv.MediaFiles)
                                            .Include(mi => (mi as Song).MediaVersions)
                                            .ThenInclude(mv => mv.MediaFiles);

        List <MediaItem> five = await mediaItems

                                // shows and seasons don't have paths to display
                                .Filter(mi => !(mi is Show))
                                .Filter(mi => !(mi is Season))
                                .OrderBy(mi => mi.Id)
                                .Take(5)
                                .ToListAsync(cancellationToken);

        if (mediaItems.Any())
        {
            var paths = new List <string>();

            foreach (MediaItem mediaItem in five)
            {
                string path = await mediaItem.GetLocalPath(
                    _plexPathReplacementService,
                    _jellyfinPathReplacementService,
                    _embyPathReplacementService,
                    false);

                paths.Add(path);
            }

            var files = string.Join(", ", paths);

            int count = await mediaItems.CountAsync(cancellationToken);

            return(WarningResult(
                       $"There are {count} items that are unavailable because ErsatzTV cannot find them on disk, including the following: {files}",
                       "/search?query=state%3aUnavailable"));
        }

        return(OkResult());
    }
Beispiel #13
0
    public async Task <Either <BaseError, string> > Handle(
        GetHlsPlaylistByChannelNumber request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        DateTimeOffset now = DateTimeOffset.Now;
        Validation <BaseError, Parameters> validation = await Validate(dbContext, request, now);

        return(await LanguageExtensions.Apply(
                   validation,
                   parameters => GetPlaylist(dbContext, request, parameters, now)));
    }
    private async Task <Option <HealthCheckResult> > VerifyProfilesUseAcceleration(
        IEnumerable <HardwareAccelerationKind> accelerationKinds)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

        List <Channel> badChannels = await dbContext.Channels
                                     .Filter(c => c.StreamingMode != StreamingMode.HttpLiveStreamingDirect)
                                     .Filter(c => !accelerationKinds.Contains(c.FFmpegProfile.HardwareAcceleration))
                                     .ToListAsync();

        if (badChannels.Any())
        {
            var accel    = string.Join(", ", accelerationKinds);
            var channels = string.Join(", ", badChannels.Map(c => $"{c.Number} - {c.Name}"));
            return(WarningResult(
                       $"The following channels are transcoding without hardware acceleration ({accel}): {channels}"));
        }

        return(None);
    }
Beispiel #15
0
    public async Task <Option <MovieViewModel> > Handle(
        GetMovieById request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Option <JellyfinMediaSource> maybeJellyfin = await _mediaSourceRepository.GetAllJellyfin()
                                                     .Map(list => list.HeadOrNone());

        Option <EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby()
                                             .Map(list => list.HeadOrNone());

        Option <Movie> maybeMovie = await _movieRepository.GetMovie(request.Id);

        Option <MediaVersion> maybeVersion = maybeMovie.Map(m => m.MediaVersions.HeadOrNone()).Flatten();
        var languageCodes = new List <string>();

        foreach (MediaVersion version in maybeVersion)
        {
            var mediaCodes = version.Streams
                             .Filter(ms => ms.MediaStreamKind == MediaStreamKind.Audio)
                             .Map(ms => ms.Language)
                             .ToList();

            languageCodes.AddRange(await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCodes));
        }

        foreach (Movie movie in maybeMovie)
        {
            string localPath = await movie.GetLocalPath(
                _plexPathReplacementService,
                _jellyfinPathReplacementService,
                _embyPathReplacementService,
                false);

            return(ProjectToViewModel(movie, localPath, languageCodes, maybeJellyfin, maybeEmby));
        }

        return(None);
    }
Beispiel #16
0
    public async Task <Either <BaseError, Unit> > Handle(
        MatchTraktListItems request,
        CancellationToken cancellationToken)
    {
        try
        {
            await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

            Validation <BaseError, TraktList> validation = await TraktListMustExist(dbContext, request.TraktListId);

            return(await validation.Match(
                       async l => await MatchListItems(dbContext, l).MapT(_ => Unit.Default),
                       error => Task.FromResult <Either <BaseError, Unit> >(error.Join())));
        }
        finally
        {
            if (request.Unlock)
            {
                _entityLocker.UnlockTrakt();
            }
        }
    }
    public async Task <LibraryPath> Add(LibraryPath libraryPath)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

        await dbContext.LibraryPaths.AddAsync(libraryPath);

        await dbContext.SaveChangesAsync();

        return(libraryPath);
    }
Beispiel #18
0
    public async Task <Option <int> > Handle(GetChannelFramerate request, CancellationToken cancellationToken)
    {
        // TODO: expand to check everything in collection rather than what's scheduled?
        _logger.LogDebug("Checking frame rates for channel {ChannelNumber}", request.ChannelNumber);

        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        List <Playout> playouts = await dbContext.Playouts
                                  .Include(p => p.Items)
                                  .ThenInclude(pi => pi.MediaItem)
                                  .ThenInclude(mi => (mi as Movie).MediaVersions)
                                  .Include(p => p.Items)
                                  .ThenInclude(pi => pi.MediaItem)
                                  .ThenInclude(mi => (mi as Episode).MediaVersions)
                                  .Include(p => p.Items)
                                  .ThenInclude(pi => pi.MediaItem)
                                  .ThenInclude(mi => (mi as Song).MediaVersions)
                                  .Include(p => p.Items)
                                  .ThenInclude(pi => pi.MediaItem)
                                  .ThenInclude(mi => (mi as MusicVideo).MediaVersions)
                                  .Include(p => p.Items)
                                  .ThenInclude(pi => pi.MediaItem)
                                  .ThenInclude(mi => (mi as OtherVideo).MediaVersions)
                                  .Filter(p => p.Channel.Number == request.ChannelNumber)
                                  .ToListAsync(cancellationToken);

        var frameRates = playouts.Map(p => p.Items.Map(i => i.MediaItem.GetHeadVersion()))
                         .Flatten()
                         .Map(mv => mv.RFrameRate)
                         .ToList();

        var distinct = frameRates.Distinct().ToList();

        if (distinct.Count > 1)
        {
            // TODO: something more intelligent than minimum framerate?
            int result = frameRates.Map(ParseFrameRate).Min();
            if (result < 24)
            {
                _logger.LogInformation(
                    "Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate} instead of min value {MinFrameRate}",
                    request.ChannelNumber,
                    distinct,
                    24,
                    result);

                return(24);
            }

            _logger.LogInformation(
                "Normalizing frame rate for channel {ChannelNumber} from {Distinct} to {FrameRate}",
                request.ChannelNumber,
                distinct,
                result);
            return(result);
        }

        _logger.LogInformation(
            "All content on channel {ChannelNumber} has the same frame rate of {FrameRate}; will not normalize",
            request.ChannelNumber,
            distinct[0]);
        return(None);
    }
    public async Task <Either <BaseError, CollectionCardResultsViewModel> > Handle(
        GetCollectionCards request,
        CancellationToken cancellationToken)
    {
        await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken);

        Option <JellyfinMediaSource> maybeJellyfin = await _mediaSourceRepository.GetAllJellyfin()
                                                     .Map(list => list.HeadOrNone());

        Option <EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby()
                                             .Map(list => list.HeadOrNone());

        return(await dbContext.Collections
               .AsNoTracking()
               .Include(c => c.CollectionItems)
               .Include(c => c.MediaItems)
               .ThenInclude(i => i.LibraryPath)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Movie).MovieMetadata)
               .ThenInclude(mm => mm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Movie).MediaVersions)
               .ThenInclude(mv => mv.MediaFiles)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Artist).ArtistMetadata)
               .ThenInclude(mvm => mvm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as MusicVideo).MusicVideoMetadata)
               .ThenInclude(mvm => mvm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as MusicVideo).Artist)
               .ThenInclude(a => a.ArtistMetadata)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as MusicVideo).MediaVersions)
               .ThenInclude(mv => mv.MediaFiles)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Show).ShowMetadata)
               .ThenInclude(sm => sm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Season).SeasonMetadata)
               .ThenInclude(sm => sm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Season).Show)
               .ThenInclude(s => s.ShowMetadata)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).EpisodeMetadata)
               .ThenInclude(em => em.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).EpisodeMetadata)
               .ThenInclude(em => em.Directors)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).EpisodeMetadata)
               .ThenInclude(em => em.Writers)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).Season)
               .ThenInclude(s => s.Show)
               .ThenInclude(s => s.ShowMetadata)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).Season)
               .ThenInclude(s => s.SeasonMetadata)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Episode).MediaVersions)
               .ThenInclude(mv => mv.MediaFiles)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as OtherVideo).OtherVideoMetadata)
               .ThenInclude(ovm => ovm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as OtherVideo).MediaVersions)
               .ThenInclude(mv => mv.MediaFiles)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Song).SongMetadata)
               .ThenInclude(ovm => ovm.Artwork)
               .Include(c => c.MediaItems)
               .ThenInclude(i => (i as Song).MediaVersions)
               .ThenInclude(mv => mv.MediaFiles)
               .SelectOneAsync(c => c.Id, c => c.Id == request.Id)
               .Map(c => c.ToEither(BaseError.New("Unable to load collection")))
               .MapT(c => ProjectToViewModel(c, maybeJellyfin, maybeEmby)));
    }