Browse Source

music video credits tweaks (#834)

* fix song subtitles

* always use generated subtitles

* file not found/unavailable fixes
pull/835/head
Jason Dove 3 years ago committed by GitHub
parent
commit
bf3f16451b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 13
      ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs
  3. 16
      ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs
  4. 4
      ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs
  5. 4
      ErsatzTV.Core/Domain/Metadata/SubtitleKind.cs
  6. 4
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  7. 17
      ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs
  8. 10
      ErsatzTV.Core/FFmpeg/MusicVideoCreditsGenerator.cs
  9. 11
      ErsatzTV.Core/FFmpeg/SubtitleBuilder.cs
  10. 4
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs
  11. 2
      ErsatzTV.Core/Interfaces/Repositories/IMediaServerTelevisionRepository.cs
  12. 5
      ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs
  13. 15
      ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs
  14. 30
      ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs
  15. 30
      ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs
  16. 30
      ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs

1
CHANGELOG.md

@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed ### Fixed
- Fix Jellyfin show library paging - Fix Jellyfin show library paging
- Properly locate and identify multiple Plex servers - Properly locate and identify multiple Plex servers
- Properly restore `Unavailable`/`File Not Found` items when they are located on disk
### Added ### Added
- Add basic music video credits subtitle generation - Add basic music video credits subtitle generation

13
ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs

@ -36,7 +36,6 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
await FFmpegProfileMustExist(dbContext, request), await FFmpegProfileMustExist(dbContext, request),
ValidatePreferredAudioLanguage(request), ValidatePreferredAudioLanguage(request),
ValidatePreferredSubtitleLanguage(request), ValidatePreferredSubtitleLanguage(request),
ValidateSubtitleAndMusicCredits(request),
await WatermarkMustExist(dbContext, request), await WatermarkMustExist(dbContext, request),
await FillerPresetMustExist(dbContext, request)) await FillerPresetMustExist(dbContext, request))
.Apply( .Apply(
@ -46,7 +45,6 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
ffmpegProfileId, ffmpegProfileId,
preferredAudioLanguageCode, preferredAudioLanguageCode,
preferredSubtitleLanguageCode, preferredSubtitleLanguageCode,
_,
watermarkId, watermarkId,
fillerPresetId) => fillerPresetId) =>
{ {
@ -109,17 +107,6 @@ public class CreateChannelHandler : IRequestHandler<CreateChannel, Either<BaseEr
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase))) ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred subtitle language code is invalid"); .ToValidation<BaseError>("Preferred subtitle language code is invalid");
private static Validation<BaseError, string> ValidateSubtitleAndMusicCredits(CreateChannel createChannel)
{
if (createChannel.MusicVideoCreditsMode != ChannelMusicVideoCreditsMode.None &&
createChannel.SubtitleMode == ChannelSubtitleMode.None)
{
return BaseError.New("Subtitles are required for music video credits");
}
return string.Empty;
}
private static async Task<Validation<BaseError, string>> ValidateNumber( private static async Task<Validation<BaseError, string>> ValidateNumber(
TvContext dbContext, TvContext dbContext,
CreateChannel createChannel) CreateChannel createChannel)

16
ErsatzTV.Application/Channels/Commands/UpdateChannelHandler.cs

@ -93,9 +93,8 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
private async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) => private async Task<Validation<BaseError, Channel>> Validate(TvContext dbContext, UpdateChannel request) =>
(await ChannelMustExist(dbContext, request), ValidateName(request), (await ChannelMustExist(dbContext, request), ValidateName(request),
await ValidateNumber(dbContext, request), await ValidateNumber(dbContext, request),
ValidatePreferredAudioLanguage(request), ValidatePreferredAudioLanguage(request))
ValidateSubtitleAndMusicCredits(request)) .Apply((channelToUpdate, _, _, _) => channelToUpdate);
.Apply((channelToUpdate, _, _, _, _) => channelToUpdate);
private static Task<Validation<BaseError, Channel>> ChannelMustExist( private static Task<Validation<BaseError, Channel>> ChannelMustExist(
TvContext dbContext, TvContext dbContext,
@ -137,15 +136,4 @@ public class UpdateChannelHandler : IRequestHandler<UpdateChannel, Either<BaseEr
lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any( lc => string.IsNullOrWhiteSpace(lc) || CultureInfo.GetCultures(CultureTypes.NeutralCultures).Any(
ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase))) ci => string.Equals(ci.ThreeLetterISOLanguageName, lc, StringComparison.OrdinalIgnoreCase)))
.ToValidation<BaseError>("Preferred audio language code is invalid"); .ToValidation<BaseError>("Preferred audio language code is invalid");
private static Validation<BaseError, string> ValidateSubtitleAndMusicCredits(UpdateChannel updateChannel)
{
if (updateChannel.MusicVideoCreditsMode != ChannelMusicVideoCreditsMode.None &&
updateChannel.SubtitleMode == ChannelSubtitleMode.None)
{
return BaseError.New("Subtitles are required for music video credits");
}
return string.Empty;
}
} }

4
ErsatzTV.Core.Tests/FFmpeg/TranscodingTests.cs

@ -566,10 +566,8 @@ public class TranscodingTests
Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask(); Optional(version.Streams.First(s => s.MediaStreamKind == MediaStreamKind.Audio)).AsTask();
public Task<Option<Domain.Subtitle>> SelectSubtitleStream( public Task<Option<Domain.Subtitle>> SelectSubtitleStream(
MediaVersion version,
List<Domain.Subtitle> subtitles, List<Domain.Subtitle> subtitles,
StreamingMode streamingMode, Channel channel,
string channelNumber,
string preferredSubtitleLanguage, string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode) => ChannelSubtitleMode subtitleMode) =>
subtitles.HeadOrNone().AsTask(); subtitles.HeadOrNone().AsTask();

4
ErsatzTV.Core/Domain/Metadata/SubtitleKind.cs

@ -3,5 +3,7 @@
public enum SubtitleKind public enum SubtitleKind
{ {
Embedded = 0, Embedded = 0,
Sidecar = 1 Sidecar = 1,
Generated = 99
} }

4
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -71,10 +71,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
preferredAudioLanguage); preferredAudioLanguage);
Option<Subtitle> maybeSubtitle = Option<Subtitle> maybeSubtitle =
await _ffmpegStreamSelector.SelectSubtitleStream( await _ffmpegStreamSelector.SelectSubtitleStream(
videoVersion,
subtitles, subtitles,
channel.StreamingMode, channel,
channel.Number,
preferredSubtitleLanguage, preferredSubtitleLanguage,
subtitleMode); subtitleMode);

17
ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs

@ -86,19 +86,24 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
} }
public async Task<Option<Subtitle>> SelectSubtitleStream( public async Task<Option<Subtitle>> SelectSubtitleStream(
MediaVersion version,
List<Subtitle> subtitles, List<Subtitle> subtitles,
StreamingMode streamingMode, Channel channel,
string channelNumber,
string preferredSubtitleLanguage, string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode) ChannelSubtitleMode subtitleMode)
{ {
if (channel.MusicVideoCreditsMode == ChannelMusicVideoCreditsMode.GenerateSubtitles &&
subtitles.FirstOrDefault(s => s.SubtitleKind == SubtitleKind.Generated) is { } generatedSubtitle)
{
_logger.LogDebug("Selecting generated subtitle for channel {Number}", channel.Number);
return Optional(generatedSubtitle);
}
if (subtitleMode == ChannelSubtitleMode.None) if (subtitleMode == ChannelSubtitleMode.None)
{ {
return None; return None;
} }
if (streamingMode == StreamingMode.HttpLiveStreamingDirect && if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect &&
string.IsNullOrWhiteSpace(preferredSubtitleLanguage)) string.IsNullOrWhiteSpace(preferredSubtitleLanguage))
{ {
// _logger.LogDebug( // _logger.LogDebug(
@ -110,7 +115,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
string language = (preferredSubtitleLanguage ?? string.Empty).ToLowerInvariant(); string language = (preferredSubtitleLanguage ?? string.Empty).ToLowerInvariant();
if (string.IsNullOrWhiteSpace(language)) if (string.IsNullOrWhiteSpace(language))
{ {
_logger.LogDebug("Channel {Number} has no preferred subtitle language code", channelNumber); _logger.LogDebug("Channel {Number} has no preferred subtitle language code", channel.Number);
} }
else else
{ {
@ -152,7 +157,7 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
_logger.LogDebug( _logger.LogDebug(
"Found no subtitles for channel {ChannelNumber} with mode {Mode} matching language {Language}", "Found no subtitles for channel {ChannelNumber} with mode {Mode} matching language {Language}",
channelNumber, channel.Number,
subtitleMode, subtitleMode,
preferredSubtitleLanguage); preferredSubtitleLanguage);

10
ErsatzTV.Core/FFmpeg/MusicVideoCreditsGenerator.cs

@ -64,12 +64,18 @@ public class MusicVideoCreditsGenerator : IMusicVideoCreditsGenerator
.WithShadow(3) .WithShadow(3)
.WithFormattedContent(sb.ToString()) .WithFormattedContent(sb.ToString())
.WithStartEnd(TimeSpan.FromSeconds(9), TimeSpan.FromSeconds(16)) .WithStartEnd(TimeSpan.FromSeconds(9), TimeSpan.FromSeconds(16))
.WithFade(true)
.BuildFile(); .BuildFile();
return new Subtitle return new Subtitle
{ {
Codec = "ass", Default = true, Forced = true, IsExtracted = false, SubtitleKind = SubtitleKind.Sidecar, Codec = "ass",
Path = subtitles, SDH = false Default = true,
Forced = true,
IsExtracted = false,
SubtitleKind = SubtitleKind.Generated,
Path = subtitles,
SDH = false
}; };
} }

11
ErsatzTV.Core/FFmpeg/SubtitleBuilder.cs

@ -10,6 +10,7 @@ public class SubtitleBuilder
private Option<int> _borderStyle; private Option<int> _borderStyle;
private string _content; private string _content;
private Option<TimeSpan> _end; private Option<TimeSpan> _end;
private bool _fade;
private Option<string> _fontName; private Option<string> _fontName;
private Option<int> _fontSize; private Option<int> _fontSize;
private int _marginLeft; private int _marginLeft;
@ -102,6 +103,12 @@ public class SubtitleBuilder
return this; return this;
} }
public SubtitleBuilder WithFade(bool fade)
{
_fade = fade;
return this;
}
public async Task<string> BuildFile() public async Task<string> BuildFile()
{ {
string fileName = _tempFilePool.GetNextTempFile(TempFileCategory.Subtitle); string fileName = _tempFilePool.GetNextTempFile(TempFileCategory.Subtitle);
@ -137,10 +144,12 @@ public class SubtitleBuilder
end = $"{(int)endTime.TotalHours:00}:{endTime.ToString(@"mm\:ss\.ff")}"; end = $"{(int)endTime.TotalHours:00}:{endTime.ToString(@"mm\:ss\.ff")}";
} }
string fade = _fade ? @"{\fad(1200, 1200)}" : string.Empty;
sb.AppendLine("[Events]"); sb.AppendLine("[Events]");
sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); sb.AppendLine("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
sb.AppendLine( sb.AppendLine(
@$"Dialogue: 0,{start},{end},Default,,{_marginLeft},{_marginRight},{_marginV},,{{\fad(1200,1200)}}{_content}"); @$"Dialogue: 0,{start},{end},Default,,{_marginLeft},{_marginRight},{_marginV},,{fade}{_content}");
await File.WriteAllTextAsync(fileName, sb.ToString()); await File.WriteAllTextAsync(fileName, sb.ToString());

4
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegStreamSelector.cs

@ -13,10 +13,8 @@ public interface IFFmpegStreamSelector
string preferredAudioLanguage); string preferredAudioLanguage);
Task<Option<Subtitle>> SelectSubtitleStream( Task<Option<Subtitle>> SelectSubtitleStream(
MediaVersion version,
List<Subtitle> subtitles, List<Subtitle> subtitles,
StreamingMode streamingMode, Channel channel,
string channelNumber,
string preferredSubtitleLanguage, string preferredSubtitleLanguage,
ChannelSubtitleMode subtitleMode); ChannelSubtitleMode subtitleMode);
} }

2
ErsatzTV.Core/Interfaces/Repositories/IMediaServerTelevisionRepository.cs

@ -19,6 +19,8 @@ public interface IMediaServerTelevisionRepository<in TLibrary, TShow, TSeason, T
Task<Unit> SetEtag(TSeason season, string etag); Task<Unit> SetEtag(TSeason season, string etag);
Task<Unit> SetEtag(TEpisode episode, string etag); Task<Unit> SetEtag(TEpisode episode, string etag);
Task<bool> FlagNormal(TLibrary library, TEpisode episode); Task<bool> FlagNormal(TLibrary library, TEpisode episode);
Task<bool> FlagNormal(TLibrary library, TSeason season);
Task<bool> FlagNormal(TLibrary library, TShow show);
Task<List<int>> FlagFileNotFoundShows(TLibrary library, List<string> showItemIds); Task<List<int>> FlagFileNotFoundShows(TLibrary library, List<string> showItemIds);
Task<List<int>> FlagFileNotFoundSeasons(TLibrary library, List<string> seasonItemIds); Task<List<int>> FlagFileNotFoundSeasons(TLibrary library, List<string> seasonItemIds);
Task<List<int>> FlagFileNotFoundEpisodes(TLibrary library, List<string> episodeItemIds); Task<List<int>> FlagFileNotFoundEpisodes(TLibrary library, List<string> episodeItemIds);

5
ErsatzTV.Core/Metadata/MediaServerMovieLibraryScanner.cs

@ -224,9 +224,10 @@ public abstract class MediaServerMovieLibraryScanner<TConnectionParameters, TLib
string existingEtag = await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty); string existingEtag = await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty);
MediaItemState existingState = await maybeExisting.Map(e => e.State).IfNoneAsync(MediaItemState.Normal); MediaItemState existingState = await maybeExisting.Map(e => e.State).IfNoneAsync(MediaItemState.Normal);
if (existingState == MediaItemState.Unavailable && existingEtag == MediaServerEtag(incoming)) if (existingState is MediaItemState.Unavailable or MediaItemState.FileNotFound &&
existingEtag == MediaServerEtag(incoming))
{ {
// skip scanning unavailable items that are unchanged and still don't exist locally // skip scanning unavailable/file not found items that are unchanged and still don't exist locally
if (!_localFileSystem.FileExists(localPath)) if (!_localFileSystem.FileExists(localPath))
{ {
return false; return false;

15
ErsatzTV.Core/Metadata/MediaServerTelevisionLibraryScanner.cs

@ -189,6 +189,11 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
await televisionRepository.SetEtag(result.Item, MediaServerEtag(incoming)); await televisionRepository.SetEtag(result.Item, MediaServerEtag(incoming));
if (await televisionRepository.FlagNormal(library, result.Item))
{
result.IsUpdated = true;
}
if (result.IsAdded || result.IsUpdated) if (result.IsAdded || result.IsUpdated)
{ {
await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id }); await _searchIndex.RebuildItems(_searchRepository, new List<int> { result.Item.Id });
@ -344,6 +349,11 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
await televisionRepository.SetEtag(result.Item, MediaServerEtag(incoming)); await televisionRepository.SetEtag(result.Item, MediaServerEtag(incoming));
if (await televisionRepository.FlagNormal(library, result.Item))
{
result.IsUpdated = true;
}
result.Item.Show = show; result.Item.Show = show;
if (result.IsAdded || result.IsUpdated) if (result.IsAdded || result.IsUpdated)
@ -484,9 +494,10 @@ public abstract class MediaServerTelevisionLibraryScanner<TConnectionParameters,
string existingEtag = await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty); string existingEtag = await maybeExisting.Map(e => e.Etag ?? string.Empty).IfNoneAsync(string.Empty);
MediaItemState existingState = await maybeExisting.Map(e => e.State).IfNoneAsync(MediaItemState.Normal); MediaItemState existingState = await maybeExisting.Map(e => e.State).IfNoneAsync(MediaItemState.Normal);
if (existingState == MediaItemState.Unavailable && existingEtag == MediaServerEtag(incoming)) if (existingState is MediaItemState.Unavailable or MediaItemState.FileNotFound &&
existingEtag == MediaServerEtag(incoming))
{ {
// skip scanning unavailable items that are unchanged and still don't exist locally // skip scanning unavailable/file not found items that are unchanged and still don't exist locally
if (!_localFileSystem.FileExists(localPath)) if (!_localFileSystem.FileExists(localPath))
{ {
return false; return false;

30
ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs

@ -209,6 +209,36 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository
new { LibraryId = library.Id, episode.ItemId }).Map(count => count > 0); new { LibraryId = library.Id, episode.ItemId }).Map(count => count > 0);
} }
public async Task<bool> FlagNormal(EmbyLibrary library, EmbySeason season)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT EmbySeason.Id FROM EmbySeason
INNER JOIN MediaItem MI ON MI.Id = EmbySeason.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE EmbySeason.ItemId = @ItemId)",
new { LibraryId = library.Id, season.ItemId }).Map(count => count > 0);
}
public async Task<bool> FlagNormal(EmbyLibrary library, EmbyShow show)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT EmbyShow.Id FROM EmbyShow
INNER JOIN MediaItem MI ON MI.Id = EmbyShow.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE EmbyShow.ItemId = @ItemId)",
new { LibraryId = library.Id, show.ItemId }).Map(count => count > 0);
}
public async Task<List<int>> FlagFileNotFoundShows(EmbyLibrary library, List<string> showItemIds) public async Task<List<int>> FlagFileNotFoundShows(EmbyLibrary library, List<string> showItemIds)
{ {
if (showItemIds.Count == 0) if (showItemIds.Count == 0)

30
ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs

@ -213,6 +213,36 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository
new { LibraryId = library.Id, episode.ItemId }).Map(count => count > 0); new { LibraryId = library.Id, episode.ItemId }).Map(count => count > 0);
} }
public async Task<bool> FlagNormal(JellyfinLibrary library, JellyfinSeason season)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT JellyfinSeason.Id FROM JellyfinSeason
INNER JOIN MediaItem MI ON MI.Id = JellyfinSeason.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE JellyfinSeason.ItemId = @ItemId)",
new { LibraryId = library.Id, season.ItemId }).Map(count => count > 0);
}
public async Task<bool> FlagNormal(JellyfinLibrary library, JellyfinShow show)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT JellyfinShow.Id FROM JellyfinShow
INNER JOIN MediaItem MI ON MI.Id = JellyfinShow.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE JellyfinShow.ItemId = @ItemId)",
new { LibraryId = library.Id, show.ItemId }).Map(count => count > 0);
}
public async Task<List<int>> FlagFileNotFoundShows(JellyfinLibrary library, List<string> showItemIds) public async Task<List<int>> FlagFileNotFoundShows(JellyfinLibrary library, List<string> showItemIds)
{ {
if (showItemIds.Count == 0) if (showItemIds.Count == 0)

30
ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs

@ -31,6 +31,36 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository
new { LibraryId = library.Id, episode.Key }).Map(count => count > 0); new { LibraryId = library.Id, episode.Key }).Map(count => count > 0);
} }
public async Task<bool> FlagNormal(PlexLibrary library, PlexSeason season)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
season.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT PlexSeason.Id FROM PlexSeason
INNER JOIN MediaItem MI ON MI.Id = PlexSeason.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE PlexSeason.Key = @Key)",
new { LibraryId = library.Id, season.Key }).Map(count => count > 0);
}
public async Task<bool> FlagNormal(PlexLibrary library, PlexShow show)
{
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();
show.State = MediaItemState.Normal;
return await dbContext.Connection.ExecuteAsync(
@"UPDATE MediaItem SET State = 0 WHERE Id IN
(SELECT PlexShow.Id FROM PlexShow
INNER JOIN MediaItem MI ON MI.Id = PlexShow.Id
INNER JOIN LibraryPath LP on MI.LibraryPathId = LP.Id AND LibraryId = @LibraryId
WHERE PlexShow.Key = @Key)",
new { LibraryId = library.Id, show.Key }).Map(count => count > 0);
}
public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexEpisode episode) public async Task<Option<int>> FlagUnavailable(PlexLibrary library, PlexEpisode episode)
{ {
await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync();

Loading…
Cancel
Save