Stream custom live channels using your own media
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

125 lines
5.2 KiB

using System.IO.Abstractions;
using System.Threading.Channels;
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Dapper;
using ErsatzTV.Application.Maintenance;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Repositories;
namespace ErsatzTV.Application.Subtitles;
public class ExtractEmbeddedShowSubtitlesHandler(
IDbContextFactory<TvContext> dbContextFactory,
ChannelWriter<IBackgroundServiceRequest> workerChannel,
IConfigElementRepository configElementRepository,
IFileSystem fileSystem,
ILogger<ExtractEmbeddedSubtitlesHandler> logger)
: ExtractEmbeddedSubtitlesHandlerBase(fileSystem, logger),
IRequestHandler<ExtractEmbeddedShowSubtitles, Option<BaseError>>
{
public async Task<Option<BaseError>> Handle(
ExtractEmbeddedShowSubtitles request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, string> validation = await FFmpegPathMustExist(dbContext, cancellationToken);
return await validation.Match(
async ffmpegPath =>
{
Option<BaseError> result = await ExtractAll(dbContext, request, ffmpegPath, cancellationToken);
await workerChannel.WriteAsync(new ReleaseMemory(false), cancellationToken);
return result;
},
error => Task.FromResult<Option<BaseError>>(error.Join()));
}
private async Task<Option<BaseError>> ExtractAll(
TvContext dbContext,
ExtractEmbeddedShowSubtitles request,
string ffmpegPath,
CancellationToken cancellationToken)
{
try
{
bool useEmbeddedSubtitles = await configElementRepository
.GetValue<bool>(ConfigElementKey.FFmpegUseEmbeddedSubtitles, cancellationToken)
.IfNoneAsync(true);
if (!useEmbeddedSubtitles)
{
return Option<BaseError>.None;
}
bool extractEmbeddedSubtitles = await configElementRepository
.GetValue<bool>(ConfigElementKey.FFmpegExtractEmbeddedSubtitles, cancellationToken)
.IfNoneAsync(false);
if (!extractEmbeddedSubtitles)
{
return Option<BaseError>.None;
}
Option<Show> maybeShow = await dbContext.Shows
.AsNoTracking()
.Include(s => s.ShowMetadata)
.SelectOneAsync(s => s.Id, s => s.Id == request.ShowId, cancellationToken);
foreach (ShowMetadata showMetadata in maybeShow.SelectMany(s => s.ShowMetadata).HeadOrNone())
{
logger.LogDebug("Checking show {ShowTitle} for text subtitles to extract", showMetadata.Title);
// check for episodes with not-extracted subtitles
List<int> episodeIds = await dbContext.Connection.QueryAsync<int>(
"""
SELECT DISTINCT E.Id
FROM Episode E
INNER JOIN Season S ON S.Id = E.SeasonId
INNER JOIN EpisodeMetadata EM ON EM.EpisodeId = E.Id
INNER JOIN Subtitle SUB ON SUB.EpisodeMetadataId = EM.Id
WHERE S.ShowId = @ShowId AND SUB.SubtitleKind=0 AND (SUB.IsExtracted=0 OR SUB.Path IS NULL)
""",
new { request.ShowId })
.Map(result => result.ToList());
// filter for items with text subtitles or font attachments
List<int> episodeIdsWithTextSubtitles =
await GetMediaItemIdsWithTextSubtitles(dbContext, episodeIds, cancellationToken);
logger.LogDebug(
"Show {ShowTitle} has {EpisodeCount} episodes with text subtitles to extract",
showMetadata.Title,
episodeIdsWithTextSubtitles.Count);
for (var i = 0; i < episodeIdsWithTextSubtitles.Count; i++)
{
logger.LogDebug(
"Extracting text subtitles for show {ShowTitle} - {Current} of {Total}",
showMetadata.Title,
i + 1,
episodeIdsWithTextSubtitles.Count);
if (cancellationToken.IsCancellationRequested)
{
return Option<BaseError>.None;
}
// extract subtitles and fonts for each item and update db
int mediaItemId = episodeIdsWithTextSubtitles[i];
await ExtractSubtitles(dbContext, mediaItemId, ffmpegPath, cancellationToken);
await ExtractFonts(dbContext, mediaItemId, ffmpegPath, cancellationToken);
}
logger.LogDebug("Done checking show {ShowTitle} for text subtitles to extract", showMetadata.Title);
}
}
catch (Exception ex) when (ex is TaskCanceledException or OperationCanceledException)
{
// do nothing
}
return Option<BaseError>.None;
}
}